adding an xpub mud
parent
50df7aab12
commit
c1893200e6
@ -0,0 +1,154 @@
|
||||
MUD Pi
|
||||
======
|
||||
|
||||
A simple text-based Multi-User Dungeon (MUD) game, which could be run on a
|
||||
Raspberry Pi or other low-end server.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
You will need to install _Python_ (2.7+ or 3.3+) where you wish to run the
|
||||
server. Installers for Windows and Mac can be found at
|
||||
<http://www.python.org/download/>. There are also tarballs for Linux, although
|
||||
the best way to install on Linux would be via the package manager.
|
||||
|
||||
To allow players to connect remotely, the server will also need to be connected
|
||||
to the internet.
|
||||
|
||||
To connect to the server you will need a telnet client. On Mac, Linux, and
|
||||
versions of Windows prior to Windows Vista, the telnet client is usually
|
||||
installed by default. For Windows Vista, 7, 8 or later, you may need to follow
|
||||
[this guide](http://technet.microsoft.com/en-us/library/cc771275%28v=ws.10%29.aspx)
|
||||
to install it.
|
||||
|
||||
|
||||
Running the Server
|
||||
------------------
|
||||
|
||||
### On Windows
|
||||
|
||||
Double click on `simplemud.py` - the file will be opened with the Python
|
||||
interpreter. To stop the server, simply close the terminal window.
|
||||
|
||||
|
||||
### On Mac OSX and Linux (including Raspberry Pi)
|
||||
|
||||
From the terminal, change to the directory containing the script and run
|
||||
|
||||
python simplemud.py
|
||||
|
||||
Note, if you are connected to the machine via SSH, you will find that the
|
||||
script stops running when you quit the SSH session. A simple way to leave the
|
||||
script running is to use a tool called `screen`. Connect via SSH as usual then
|
||||
run `screen`. You will enter what looks like a normal shell prompt, but now you
|
||||
can start the python script running and hit `ctl+a` followed by `d` to leave
|
||||
_screen_ running in the background. The next time you connect, you can
|
||||
re-attach to your screen session using `screen -r`. Alternatively you could
|
||||
[create a daemon script](http://jimmyg.org/blog/2010/python-daemon-init-script.html)
|
||||
to run the script in the background every time the server starts.
|
||||
|
||||
|
||||
Connecting to the Server
|
||||
------------------------
|
||||
|
||||
If the server is running behind a NAT such as a home router, you will need to
|
||||
set up port **1234** to be forwarded to the machine running the server. See your
|
||||
router's instructions for how to set this up. There are a large number of
|
||||
setup guides for different models of router here:
|
||||
<http://portforward.com/english/routers/port_forwarding/>
|
||||
|
||||
You will need to know the _external_ IP address of the machine running the
|
||||
server. This can be discovered by visiting <http://www.whatsmyip.org> from
|
||||
that machine.
|
||||
|
||||
To connect to the server, open your operating system's terminal or command
|
||||
prompt and start the telnet client by running:
|
||||
|
||||
telnet <ip address> 1234
|
||||
|
||||
where `<ip address>` is the external IP address of the server, as described
|
||||
above. 1234 is the port number that the server listens on.
|
||||
|
||||
If you are using Windows Vista, 7, 8 or later and get the message:
|
||||
|
||||
'telnet' is not recognized as an internal or external command, operable
|
||||
program or batch file.
|
||||
|
||||
then follow
|
||||
[this guide](http://technet.microsoft.com/en-us/library/cc771275%28v=ws.10%29.aspx)
|
||||
to install the Windows telnet client.
|
||||
|
||||
If all goes well, you should be presented with the message
|
||||
|
||||
What is your name?
|
||||
|
||||
To quit the telnet client, press `ctl + ]` to go to the prompt, and then
|
||||
type `quit`.
|
||||
|
||||
|
||||
What is Telnet?
|
||||
---------------
|
||||
|
||||
Telnet is simple text-based network communication protocol that was invented in
|
||||
1969 and has since been superseded by other, more secure protocols. It does
|
||||
remain popular for a few specialised uses however, MUD games being one of these
|
||||
uses. A long (and boring) history of the telnet protocol can be found here:
|
||||
<http://www.cs.utexas.edu/users/chris/think/ARPANET/Telnet/Telnet.shtml>
|
||||
|
||||
|
||||
What is a MUD?
|
||||
--------------
|
||||
|
||||
MUD is short for Multi-User Dungeon. A MUD is a text-based online role-playing
|
||||
game. MUDs were popular in the early 80s and were the precursor to the
|
||||
graphical Massively-Multiplayer Online Role-Playing Games we have today, like
|
||||
World of Warcraft. <http://www.mudconnect.com> is a great site for learning
|
||||
more about MUDs.
|
||||
|
||||
|
||||
Extending the Game
|
||||
------------------
|
||||
|
||||
MUD Pi is a free and open source project (that's _free_ as in _freedom_). This
|
||||
means that the source code is included and you are free to read it, copy it,
|
||||
extend it and use it as a starting point for your own MUD game or any other
|
||||
project. See `licence.md` for more info.
|
||||
|
||||
MUD Pi was written in the Python programming language. If you have never used
|
||||
Python before, or are new to programming in general, why not try an online
|
||||
tutorial, such as <http://www.learnpython.org/>.
|
||||
|
||||
There are 2 source files in the project. `mudserver.py` is a module containing
|
||||
the `MudServer` class - a basic server script which handles player connections
|
||||
and sending and receiving messages. `simplemud.py` is an example game using
|
||||
`MudServer`, with player chat and rooms to move between.
|
||||
|
||||
The best place to start tweaking the game would be to have a look at
|
||||
`simplemud.py`. Why not try adding more rooms to the game world? You'll find
|
||||
more ideas for things to try in the source code itself.
|
||||
|
||||
Of course if you're feeling more adventurous you could take a look at the
|
||||
slightly more advanced networking code in `mudserver.py`.
|
||||
|
||||
|
||||
MUD-Pi-Based Projects
|
||||
---------------------
|
||||
|
||||
Here are some of the cool projects people have made from MUD-Pi:
|
||||
|
||||
* **[ESP8266 MUD](http://git.savsoul.com/barry/esp8266-Mud) by Barry Ruffner** -
|
||||
a MUD that runs entirely within an ESP8266 microchip, using MicroPython
|
||||
* **[MuddySwamp](https://github.com/ufosc/MuddySwamp) by the University of**
|
||||
**Florida Open Source Club** - a UF-themed MUD
|
||||
* **[Dumserver](https://github.com/wowpin/dumserver) by Bartek Radwanski** -
|
||||
a feature-rich MUD engine
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
MUD Pi was written by Mark Frimston
|
||||
|
||||
For feedback, please email <mfrimston@gmail.com> or add a comment on the
|
||||
project's [Github page](http://github.com/frimkron/mud-pi)
|
Binary file not shown.
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2013 Mark Frimston
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,405 @@
|
||||
"""Basic MUD server module for creating text-based Multi-User Dungeon
|
||||
(MUD) games.
|
||||
|
||||
Contains one class, MudServer, which can be instantiated to start a
|
||||
server running then used to send and receive messages from players.
|
||||
|
||||
author: Mark Frimston - mfrimston@gmail.com
|
||||
"""
|
||||
|
||||
|
||||
import socket
|
||||
import select
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
class MudServer(object):
|
||||
"""A basic server for text-based Multi-User Dungeon (MUD) games.
|
||||
|
||||
Once created, the server will listen for players connecting using
|
||||
Telnet. Messages can then be sent to and from multiple connected
|
||||
players.
|
||||
|
||||
The 'update' method should be called in a loop to keep the server
|
||||
running.
|
||||
"""
|
||||
|
||||
# An inner class which is instantiated for each connected client to store
|
||||
# info about them
|
||||
|
||||
class _Client(object):
|
||||
"""Holds information about a connected player"""
|
||||
|
||||
# the socket object used to communicate with this client
|
||||
socket = None
|
||||
# the ip address of this client
|
||||
address = ""
|
||||
# holds data send from the client until a full message is received
|
||||
buffer = ""
|
||||
# the last time we checked if the client was still connected
|
||||
lastcheck = 0
|
||||
|
||||
def __init__(self, socket, address, buffer, lastcheck):
|
||||
self.socket = socket
|
||||
self.address = address
|
||||
self.buffer = buffer
|
||||
self.lastcheck = lastcheck
|
||||
|
||||
# Used to store different types of occurences
|
||||
_EVENT_NEW_PLAYER = 1
|
||||
_EVENT_PLAYER_LEFT = 2
|
||||
_EVENT_COMMAND = 3
|
||||
|
||||
# Different states we can be in while reading data from client
|
||||
# See _process_sent_data function
|
||||
_READ_STATE_NORMAL = 1
|
||||
_READ_STATE_COMMAND = 2
|
||||
_READ_STATE_SUBNEG = 3
|
||||
|
||||
# Command codes used by Telnet protocol
|
||||
# See _process_sent_data function
|
||||
_TN_INTERPRET_AS_COMMAND = 255
|
||||
_TN_ARE_YOU_THERE = 246
|
||||
_TN_WILL = 251
|
||||
_TN_WONT = 252
|
||||
_TN_DO = 253
|
||||
_TN_DONT = 254
|
||||
_TN_SUBNEGOTIATION_START = 250
|
||||
_TN_SUBNEGOTIATION_END = 240
|
||||
|
||||
# socket used to listen for new clients
|
||||
_listen_socket = None
|
||||
# holds info on clients. Maps client id to _Client object
|
||||
_clients = {}
|
||||
# counter for assigning each client a new id
|
||||
_nextid = 0
|
||||
# list of occurences waiting to be handled by the code
|
||||
_events = []
|
||||
# list of newly-added occurences
|
||||
_new_events = []
|
||||
|
||||
def __init__(self):
|
||||
"""Constructs the MudServer object and starts listening for
|
||||
new players.
|
||||
"""
|
||||
|
||||
self._clients = {}
|
||||
self._nextid = 0
|
||||
self._events = []
|
||||
self._new_events = []
|
||||
|
||||
# create a new tcp socket which will be used to listen for new clients
|
||||
self._listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# set a special option on the socket which allows the port to be
|
||||
# immediately without having to wait
|
||||
self._listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
|
||||
1)
|
||||
|
||||
# bind the socket to an ip address and port. Port 23 is the standard
|
||||
# telnet port which telnet clients will use, however on some platforms
|
||||
# this requires root permissions, so we use a higher arbitrary port
|
||||
# number instead: 1234. Address 0.0.0.0 means that we will bind to all
|
||||
# of the available network interfaces
|
||||
self._listen_socket.bind(("0.0.0.0", 1234))
|
||||
|
||||
# set to non-blocking mode. This means that when we call 'accept', it
|
||||
# will return immediately without waiting for a connection
|
||||
self._listen_socket.setblocking(False)
|
||||
|
||||
# start listening for connections on the socket
|
||||
self._listen_socket.listen(1)
|
||||
|
||||
def update(self):
|
||||
"""Checks for new players, disconnected players, and new
|
||||
messages sent from players. This method must be called before
|
||||
up-to-date info can be obtained from the 'get_new_players',
|
||||
'get_disconnected_players' and 'get_commands' methods.
|
||||
It should be called in a loop to keep the game running.
|
||||
"""
|
||||
|
||||
# check for new stuff
|
||||
self._check_for_new_connections()
|
||||
self._check_for_disconnected()
|
||||
self._check_for_messages()
|
||||
|
||||
# move the new events into the main events list so that they can be
|
||||
# obtained with 'get_new_players', 'get_disconnected_players' and
|
||||
# 'get_commands'. The previous events are discarded
|
||||
self._events = list(self._new_events)
|
||||
self._new_events = []
|
||||
|
||||
def get_new_players(self):
|
||||
"""Returns a list containing info on any new players that have
|
||||
entered the game since the last call to 'update'. Each item in
|
||||
the list is a player id number.
|
||||
"""
|
||||
retval = []
|
||||
# go through all the events in the main list
|
||||
for ev in self._events:
|
||||
# if the event is a new player occurence, add the info to the list
|
||||
if ev[0] == self._EVENT_NEW_PLAYER:
|
||||
retval.append(ev[1])
|
||||
# return the info list
|
||||
return retval
|
||||
|
||||
def get_disconnected_players(self):
|
||||
"""Returns a list containing info on any players that have left
|
||||
the game since the last call to 'update'. Each item in the list
|
||||
is a player id number.
|
||||
"""
|
||||
retval = []
|
||||
# go through all the events in the main list
|
||||
for ev in self._events:
|
||||
# if the event is a player disconnect occurence, add the info to
|
||||
# the list
|
||||
if ev[0] == self._EVENT_PLAYER_LEFT:
|
||||
retval.append(ev[1])
|
||||
# return the info list
|
||||
return retval
|
||||
|
||||
def get_commands(self):
|
||||
"""Returns a list containing any commands sent from players
|
||||
since the last call to 'update'. Each item in the list is a
|
||||
3-tuple containing the id number of the sending player, a
|
||||
string containing the command (i.e. the first word of what
|
||||
they typed), and another string containing the text after the
|
||||
command
|
||||
"""
|
||||
retval = []
|
||||
# go through all the events in the main list
|
||||
for ev in self._events:
|
||||
# if the event is a command occurence, add the info to the list
|
||||
if ev[0] == self._EVENT_COMMAND:
|
||||
retval.append((ev[1], ev[2], ev[3]))
|
||||
# return the info list
|
||||
return retval
|
||||
|
||||
def send_message(self, to, message):
|
||||
"""Sends the text in the 'message' parameter to the player with
|
||||
the id number given in the 'to' parameter. The text will be
|
||||
printed out in the player's terminal.
|
||||
"""
|
||||
# we make sure to put a newline on the end so the client receives the
|
||||
# message on its own line
|
||||
self._attempt_send(to, message+"\n\r")
|
||||
|
||||
def shutdown(self):
|
||||
"""Closes down the server, disconnecting all clients and
|
||||
closing the listen socket.
|
||||
"""
|
||||
# for each client
|
||||
for cl in self._clients.values():
|
||||
# close the socket, disconnecting the client
|
||||
cl.socket.shutdown(socket.SHUT_RDWR)
|
||||
cl.socket.close()
|
||||
# stop listening for new clients
|
||||
self._listen_socket.close()
|
||||
|
||||
def _attempt_send(self, clid, data):
|
||||
# python 2/3 compatability fix - convert non-unicode string to unicode
|
||||
if sys.version < '3' and type(data) != unicode:
|
||||
data = unicode(data, "latin1")
|
||||
try:
|
||||
# look up the client in the client map and use 'sendall' to send
|
||||
# the message string on the socket. 'sendall' ensures that all of
|
||||
# the data is sent in one go
|
||||
self._clients[clid].socket.sendall(bytearray(data, "latin1"))
|
||||
# KeyError will be raised if there is no client with the given id in
|
||||
# the map
|
||||
except KeyError:
|
||||
pass
|
||||
# If there is a connection problem with the client (e.g. they have
|
||||
# disconnected) a socket error will be raised
|
||||
except socket.error:
|
||||
self._handle_disconnect(clid)
|
||||
|
||||
def _check_for_new_connections(self):
|
||||
|
||||
# 'select' is used to check whether there is data waiting to be read
|
||||
# from the socket. We pass in 3 lists of sockets, the first being those
|
||||
# to check for readability. It returns 3 lists, the first being
|
||||
# the sockets that are readable. The last parameter is how long to wait
|
||||
# - we pass in 0 so that it returns immediately without waiting
|
||||
rlist, wlist, xlist = select.select([self._listen_socket], [], [], 0)
|
||||
|
||||
# if the socket wasn't in the readable list, there's no data available,
|
||||
# meaning no clients waiting to connect, and so we can exit the method
|
||||
# here
|
||||
if self._listen_socket not in rlist:
|
||||
return
|
||||
|
||||
# 'accept' returns a new socket and address info which can be used to
|
||||
# communicate with the new client
|
||||
joined_socket, addr = self._listen_socket.accept()
|
||||
|
||||
# set non-blocking mode on the new socket. This means that 'send' and
|
||||
# 'recv' will return immediately without waiting
|
||||
joined_socket.setblocking(False)
|
||||
|
||||
# construct a new _Client object to hold info about the newly connected
|
||||
# client. Use 'nextid' as the new client's id number
|
||||
self._clients[self._nextid] = MudServer._Client(joined_socket, addr[0],
|
||||
"", time.time())
|
||||
|
||||
# add a new player occurence to the new events list with the player's
|
||||
# id number
|
||||
self._new_events.append((self._EVENT_NEW_PLAYER, self._nextid))
|
||||
|
||||
# add 1 to 'nextid' so that the next client to connect will get a
|
||||
# unique id number
|
||||
self._nextid += 1
|
||||
|
||||
def _check_for_disconnected(self):
|
||||
|
||||
# go through all the clients
|
||||
for id, cl in list(self._clients.items()):
|
||||
|
||||
# if we last checked the client less than 5 seconds ago, skip this
|
||||
# client and move on to the next one
|
||||
if time.time() - cl.lastcheck < 5.0:
|
||||
continue
|
||||
|
||||
# send the client an invisible character. It doesn't actually
|
||||
# matter what we send, we're really just checking that data can
|
||||
# still be written to the socket. If it can't, an error will be
|
||||
# raised and we'll know that the client has disconnected.
|
||||
self._attempt_send(id, "\x00")
|
||||
|
||||
# update the last check time
|
||||
cl.lastcheck = time.time()
|
||||
|
||||
def _check_for_messages(self):
|
||||
|
||||
# go through all the clients
|
||||
for id, cl in list(self._clients.items()):
|
||||
|
||||
# we use 'select' to test whether there is data waiting to be read
|
||||
# from the client socket. The function takes 3 lists of sockets,
|
||||
# the first being those to test for readability. It returns 3 list
|
||||
# of sockets, the first being those that are actually readable.
|
||||
rlist, wlist, xlist = select.select([cl.socket], [], [], 0)
|
||||
|
||||
# if the client socket wasn't in the readable list, there is no
|
||||
# new data from the client - we can skip it and move on to the next
|
||||
# one
|
||||
if cl.socket not in rlist:
|
||||
continue
|
||||
|
||||
try:
|
||||
# read data from the socket, using a max length of 4096
|
||||
data = cl.socket.recv(4096).decode("latin1")
|
||||
|
||||
# process the data, stripping out any special Telnet commands
|
||||
message = self._process_sent_data(cl, data)
|
||||
|
||||
# if there was a message in the data
|
||||
if message:
|
||||
|
||||
# remove any spaces, tabs etc from the start and end of
|
||||
# the message
|
||||
message = message.strip()
|
||||
|
||||
# separate the message into the command (the first word)
|
||||
# and its parameters (the rest of the message)
|
||||
command, params = (message.split(" ", 1) + ["", ""])[:2]
|
||||
|
||||
# add a command occurence to the new events list with the
|
||||
# player's id number, the command and its parameters
|
||||
self._new_events.append((self._EVENT_COMMAND, id,
|
||||
command.lower(), params))
|
||||
|
||||
# if there is a problem reading from the socket (e.g. the client
|
||||
# has disconnected) a socket error will be raised
|
||||
except socket.error:
|
||||
self._handle_disconnect(id)
|
||||
|
||||
def _handle_disconnect(self, clid):
|
||||
|
||||
# remove the client from the clients map
|
||||
del(self._clients[clid])
|
||||
|
||||
# add a 'player left' occurence to the new events list, with the
|
||||
# player's id number
|
||||
self._new_events.append((self._EVENT_PLAYER_LEFT, clid))
|
||||
|
||||
def _process_sent_data(self, client, data):
|
||||
|
||||
# the Telnet protocol allows special command codes to be inserted into
|
||||
# messages. For our very simple server we don't need to response to
|
||||
# any of these codes, but we must at least detect and skip over them
|
||||
# so that we don't interpret them as text data.
|
||||
# More info on the Telnet protocol can be found here:
|
||||
# http://pcmicro.com/netfoss/telnet.html
|
||||
|
||||
# start with no message and in the normal state
|
||||
message = None
|
||||
state = self._READ_STATE_NORMAL
|
||||
|
||||
# go through the data a character at a time
|
||||
for c in data:
|
||||
|
||||
# handle the character differently depending on the state we're in:
|
||||
|
||||
# normal state
|
||||
if state == self._READ_STATE_NORMAL:
|
||||
|
||||
# if we received the special 'interpret as command' code,
|
||||
# switch to 'command' state so that we handle the next
|
||||
# character as a command code and not as regular text data
|
||||
if ord(c) == self._TN_INTERPRET_AS_COMMAND:
|
||||
state = self._READ_STATE_COMMAND
|
||||
|
||||
# if we get a newline character, this is the end of the
|
||||
# message. Set 'message' to the contents of the buffer and
|
||||
# clear the buffer
|
||||
elif c == "\n":
|
||||
message = client.buffer
|
||||
client.buffer = ""
|
||||
|
||||
# some telnet clients send the characters as soon as the user
|
||||
# types them. So if we get a backspace character, this is where
|
||||
# the user has deleted a character and we should delete the
|
||||
# last character from the buffer.
|
||||
elif c == "\x08":
|
||||
client.buffer = client.buffer[:-1]
|
||||
|
||||
# otherwise it's just a regular character - add it to the
|
||||
# buffer where we're building up the received message
|
||||
else:
|
||||
client.buffer += c
|
||||
|
||||
# command state
|
||||
elif state == self._READ_STATE_COMMAND:
|
||||
|
||||
# the special 'start of subnegotiation' command code indicates
|
||||
# that the following characters are a list of options until
|
||||
# we're told otherwise. We switch into 'subnegotiation' state
|
||||
# to handle this
|
||||
if ord(c) == self._TN_SUBNEGOTIATION_START:
|
||||
state = self._READ_STATE_SUBNEG
|
||||
|
||||
# if the command code is one of the 'will', 'wont', 'do' or
|
||||
# 'dont' commands, the following character will be an option
|
||||
# code so we must remain in the 'command' state
|
||||
elif ord(c) in (self._TN_WILL, self._TN_WONT, self._TN_DO,
|
||||
self._TN_DONT):
|
||||
state = self._READ_STATE_COMMAND
|
||||
|
||||
# for all other command codes, there is no accompanying data so
|
||||
# we can return to 'normal' state.
|
||||
else:
|
||||
state = self._READ_STATE_NORMAL
|
||||
|
||||
# subnegotiation state
|
||||
elif state == self._READ_STATE_SUBNEG:
|
||||
|
||||
# if we reach an 'end of subnegotiation' command, this ends the
|
||||
# list of options and we can return to 'normal' state.
|
||||
# Otherwise we must remain in this state
|
||||
if ord(c) == self._TN_SUBNEGOTIATION_END:
|
||||
state = self._READ_STATE_NORMAL
|
||||
|
||||
# return the contents of 'message' which is either a string or None
|
||||
return message
|
@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""A simple Multi-User Dungeon (MUD) game. Players can talk to each
|
||||
other, examine their surroundings and move between rooms.
|
||||
|
||||
Some ideas for things to try adding:
|
||||
* More rooms to explore
|
||||
* An 'emote' command e.g. 'emote laughs out loud' -> 'Mark laughs
|
||||
out loud'
|
||||
* A 'whisper' command for talking to individual players
|
||||
* A 'shout' command for yelling to players in all rooms
|
||||
* Items to look at in rooms e.g. 'look fireplace' -> 'You see a
|
||||
roaring, glowing fire'
|
||||
* Items to pick up e.g. 'take rock' -> 'You pick up the rock'
|
||||
* Monsters to fight
|
||||
* Loot to collect
|
||||
* Saving players accounts between sessions
|
||||
* A password login
|
||||
* A shop from which to buy items
|
||||
|
||||
author: Mark Frimston - mfrimston@gmail.com
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
# import the MUD server class
|
||||
from mudserver import MudServer
|
||||
|
||||
|
||||
# structure defining the rooms in the game. Try adding more rooms to the game!
|
||||
rooms = {
|
||||
"studio": {
|
||||
"description": "You're in the XPUB studio. Someone is making coffee in the back.",
|
||||
"exits": {"south": "neutral zone"}
|
||||
},
|
||||
"office": {
|
||||
"description": "You're in the office. Leslie is on the phone.",
|
||||
"exits": {"north": "neutral zone"}
|
||||
},
|
||||
"neutral zone": {
|
||||
"description": "You're in the neutral zone. Some stuff happens.",
|
||||
"exits": {
|
||||
"south": "office",
|
||||
"north": "studio",
|
||||
"west" : "artificial research station"
|
||||
}
|
||||
},
|
||||
"artificial research station": {
|
||||
"description": "You're in the artificial research station. What happens here?",
|
||||
"exits": {"south": "neutral zone"}
|
||||
}
|
||||
}
|
||||
|
||||
# stores the players in the game
|
||||
players = {}
|
||||
|
||||
# start the server
|
||||
mud = MudServer()
|
||||
|
||||
# main game loop. We loop forever (i.e. until the program is terminated)
|
||||
while True:
|
||||
|
||||
# pause for 1/5 of a second on each loop, so that we don't constantly
|
||||
# use 100% CPU time
|
||||
time.sleep(0.2)
|
||||
|
||||
# 'update' must be called in the loop to keep the game running and give
|
||||
# us up-to-date information
|
||||
mud.update()
|
||||
|
||||
# go through any newly connected players
|
||||
for id in mud.get_new_players():
|
||||
|
||||
# add the new player to the dictionary, noting that they've not been
|
||||
# named yet.
|
||||
# The dictionary key is the player's id number. We set their room to
|
||||
# None initially until they have entered a name
|
||||
# Try adding more player stats - level, gold, inventory, etc
|
||||
players[id] = {
|
||||
"name": None,
|
||||
"room": None,
|
||||
}
|
||||
|
||||
# send the new player a prompt for their name
|
||||
mud.send_message(id, "What is your name?")
|
||||
|
||||
# go through any recently disconnected players
|
||||
for id in mud.get_disconnected_players():
|
||||
|
||||
# if for any reason the player isn't in the player map, skip them and
|
||||
# move on to the next one
|
||||
if id not in players:
|
||||
continue
|
||||
|
||||
# go through all the players in the game
|
||||
for pid, pl in players.items():
|
||||
# send each player a message to tell them about the diconnected
|
||||
# player
|
||||
mud.send_message(pid, "{} quit the game".format(players[id]["name"]))
|
||||
|
||||
# remove the player's entry in the player dictionary
|
||||
del(players[id])
|
||||
|
||||
# go through any new commands sent from players
|
||||
for id, command, params in mud.get_commands():
|
||||
|
||||
# if for any reason the player isn't in the player map, skip them and
|
||||
# move on to the next one
|
||||
if id not in players:
|
||||
continue
|
||||
|
||||
# if the player hasn't given their name yet, use this first command as
|
||||
# their name and move them to the starting room.
|
||||
if players[id]["name"] is None:
|
||||
|
||||
players[id]["name"] = command
|
||||
# this is the room in which the game starts
|
||||
players[id]["room"] = "studio"
|
||||
|
||||
# go through all the players in the game
|
||||
for pid, pl in players.items():
|
||||
# send each player a message to tell them about the new player
|
||||
mud.send_message(pid, "{} entered the game".format(
|
||||
players[id]["name"]))
|
||||
|
||||
# send the new player a welcome message
|
||||
mud.send_message(id, "Welcome to the game, {}. ".format(
|
||||
players[id]["name"])
|
||||
+ "Type 'help' for a list of commands. Have fun!\n")
|
||||
|
||||
# send the new player the description of their current room
|
||||
mud.send_message(id, rooms[players[id]["room"]]["description"])
|
||||
|
||||
# each of the possible commands is handled below. Try adding new
|
||||
# commands to the game!
|
||||
|
||||
# 'help' command
|
||||
elif command == "help":
|
||||
|
||||
# send the player back the list of possible commands
|
||||
mud.send_message(id, "Commands:")
|
||||
mud.send_message(id, " say <message> - Says something out loud, "
|
||||
+ "e.g. 'say Hello'")
|
||||
mud.send_message(id, " look - Examines the "
|
||||
+ "surroundings, e.g. 'look'")
|
||||
mud.send_message(id, " go <exit> - Moves through the exit "
|
||||
+ "specified, e.g. 'go outside'")
|
||||
mud.send_message(id, " create <exit> <new roomname> - Creates a new exit and room")
|
||||
mud.send_message(id, " describe <description> - Change the description of the current room")
|
||||
|
||||
# 'say' command
|
||||
elif command == "say":
|
||||
|
||||
# go through every player in the game
|
||||
for pid, pl in players.items():
|
||||
# if they're in the same room as the player
|
||||
if players[pid]["room"] == players[id]["room"]:
|
||||
# send them a message telling them what the player said
|
||||
mud.send_message(pid, "{} says: {}".format(
|
||||
players[id]["name"], params))
|
||||
|
||||
# 'look' command
|
||||
elif command == "look":
|
||||
|
||||
# store the player's current room
|
||||
current_room = rooms[players[id]["room"]]
|
||||
|
||||
# send the player back the description of their current room
|
||||
mud.send_message(id, current_room["description"])
|
||||
|
||||
playershere = []
|
||||
# go through every player in the game
|
||||
for pid, pl in players.items():
|
||||
# if they're in the same room as the player
|
||||
if players[pid]["room"] == players[id]["room"]:
|
||||
# ... and they have a name to be shown
|
||||
if players[pid]["name"] is not None:
|
||||
# add their name to the list
|
||||
playershere.append(players[pid]["name"])
|
||||
|
||||
# send player a message containing the list of players in the room
|
||||
playershere_string = ", ".join(playershere)
|
||||
mud.send_message(id, f"Players here: { playershere_string }")
|
||||
|
||||
# send player a message containing the list of exits from this room
|
||||
exits = ", ".join(current_room["exits"])
|
||||
mud.send_message(id, f"Exits are: { exits }")
|
||||
|
||||
# 'go' command
|
||||
elif command == "go":
|
||||
|
||||
# store the exit name
|
||||
ex = params.lower()
|
||||
|
||||
# store the player's current room
|
||||
current_room = rooms[players[id]["room"]]
|
||||
|
||||
# if the specified exit is found in the room's exits list
|
||||
if ex in current_room["exits"]:
|
||||
|
||||
# go through all the players in the game
|
||||
for pid, pl in players.items():
|
||||
# if player is in the same room and isn't the player
|
||||
# sending the command
|
||||
if players[pid]["room"] == players[id]["room"] and pid != id:
|
||||
# send them a message telling them that the player
|
||||
# left the room
|
||||
mud.send_message(pid, "{} left via exit '{}'".format(players[id]["name"], ex))
|
||||
|
||||
# update the player's current room to the one the exit leads to
|
||||
players[id]["room"] = current_room["exits"][ex]
|
||||
current_room = rooms[players[id]["room"]]
|
||||
|
||||
# go through all the players in the game
|
||||
for pid, pl in players.items():
|
||||
# if player is in the same (new) room and isn't the player
|
||||
# sending the command
|
||||
if players[pid]["room"] == players[id]["room"] \
|
||||
and pid != id:
|
||||
# send them a message telling them that the player
|
||||
# entered the room
|
||||
mud.send_message(pid,
|
||||
"{} arrived via exit '{}'".format(
|
||||
players[id]["name"], ex))
|
||||
|
||||
# send the player a message telling them where they are now
|
||||
mud.send_message(id, "You arrive at '{}'".format(
|
||||
players[id]["room"]))
|
||||
|
||||
# the specified exit wasn't found in the current room
|
||||
else:
|
||||
# send back an 'unknown exit' message
|
||||
mud.send_message(id, "Unknown exit '{}'".format(ex))
|
||||
|
||||
# 'create' command
|
||||
elif command == "create":
|
||||
|
||||
# store the exit or room that will be created
|
||||
parameters = params.lower()
|
||||
parameters_list = parameters.split()
|
||||
print("[INSPECT] parameters: ", parameters_list)
|
||||
|
||||
if len(parameters_list) >= 1:
|
||||
# store the new exit name
|
||||
new_exit = parameters_list[0]
|
||||
print("[INSPECT] new exit: ", new_exit)
|
||||
else:
|
||||
new_exit = None
|
||||
|
||||
if len(parameters_list) >= 2:
|
||||
# store the new room name
|
||||
new_room = " ".join(parameters_list[1:])
|
||||
print("[INSPECT] new room: ", new_room)
|
||||
else:
|
||||
new_exit = None
|
||||
|
||||
# store the player's current room
|
||||
current_room = players[id]["room"]
|
||||
print("[INSPECT] current room: ", current_room)
|
||||
|
||||
# store information about the player's current room
|
||||
current_room_dict = rooms[players[id]["room"]]
|
||||
print("[INSPECT] current room dict: ", current_room_dict)
|
||||
|
||||
# if both the new exit and new room are given
|
||||
if new_exit is not None and new_room is not None:
|
||||
|
||||
# send player a message when the exit already exists
|
||||
if new_exit in current_room_dict["exits"]:
|
||||
mud.send_message(id, "This exit already exist.")
|
||||
exits = ", ".join(current_room["exits"])
|
||||
|
||||
# create new room
|
||||
else:
|
||||
print(f"[INSPECT] Make new room: { new_room }, in the direction: { new_exit }")
|
||||
|
||||
# add the new exit to the current room
|
||||
rooms[current_room]["exits"][new_exit] = new_room
|
||||
|
||||
# store information about the new room
|
||||
rooms[new_room] = {}
|
||||
rooms[new_room]["description"] = ""
|
||||
rooms[new_room]["exits"] = {}
|
||||
|
||||
# add the opposite exit direction to the exits of the new room
|
||||
if new_exit == "west":
|
||||
exit_to_add = "east"
|
||||
elif new_exit == "east":
|
||||
exit_to_add = "east"
|
||||
if new_exit == "north":
|
||||
exit_to_add = "south"
|
||||
elif new_exit == "south":
|
||||
exit_to_add = "north"
|
||||
|
||||
# store this exit to the new room
|
||||
rooms[new_room]["exits"][exit_to_add] = current_room
|
||||
|
||||
# announce the new room to the player
|
||||
mud.send_message(id, f"A new room is added: { new_room } (in the { new_exit })")
|
||||
# invite the player to write a description for the room
|
||||
mud.send_message(id, "The room is not described yet. When you are in the room, you can use 'describe' to add a description. For example: 'describe This is the XML! It smells a bit muffy here.'")
|
||||
|
||||
# warn the player when the "create" command is not used in the right way
|
||||
else:
|
||||
mud.send_message(id, f"Sorry you cannot create a new room in that way. Try: 'create direction roomname'")
|
||||
|
||||
# 'describe' command
|
||||
elif command == "describe":
|
||||
|
||||
# store the exit or room that will be created
|
||||
description = params.lower()
|
||||
print("[INSPECT] description: ", description)
|
||||
|
||||
# store the player's current room
|
||||
current_room = players[id]["room"]
|
||||
print("[INSPECT] current room: ", current_room)
|
||||
|
||||
rooms[new_room]["description"] = description
|
||||
|
||||
# some other, unrecognised command
|
||||
else:
|
||||
# send back an 'unknown command' message
|
||||
mud.send_message(id, "Unknown command '{}'".format(command))
|
Loading…
Reference in New Issue