main.py 9.46 KB
#!/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 datetime
#import io
import json

# import the MUD server class
from mudserver import MudServer
from commands import CommandHandler

print('STARTING MUD\r\n\r\n\r\n')

rooms = {}

# stores the players in the game
players = {}

# start the server
mud = MudServer()

cmd_handler = CommandHandler()

def load_room(room_name):
    print("Loading room:" + room_name)
    try:
        if room_name in rooms:
            return rooms[room_name]


        module = __import__('rooms.' + room_name.lower())
        room_module = getattr(module, room_name.lower())
        room_class = getattr(room_module, room_name)
        instance = room_class()
        rooms[room_name] = instance

        return instance
    except Exception as e:
        print(e)
        return None

def save_object_to_file(obj, filename):
    with open(filename, 'w', encoding='utf-8') as f:
      f.write(json.dumps(obj, ensure_ascii=False))

def load_object_from_file(filename):
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            return json.loads(f.read())
    except Exception:
        return None

def prompt(pid):
    if "prompt" not in players[pid]:
        players[pid]["prompt"] = "> "
    mud.send_message(pid, "\r\n" + players[pid]["prompt"], '')

# 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.001)

    # TODO: Add some cache removal if older than X
    # now = datetime.datetime.now()
    # delta = now - datetime.timedelta(minutes = 2)
    # for room in rooms:
    #     if room.created < delta:
    #         del rooms[room]

    # '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,
            "inventory": None,
            "prompt": "> ",
        }

        # 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 players[id]["room"]:
            rm = load_room(players[id]["room"])
        # 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:

            loaded_player = load_object_from_file("players/{}.json".format(command))
            if loaded_player is None:            
                players[id]["name"] = command
                players[id]["room"] = "Tavern"
            else:
                players[id] = loaded_player

            # 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, "\r\n\r\nWelcome to the game, {}. ".format(
                                                           players[id]["name"])
                             + "\r\nType 'help' for a list of commands. Have fun!\r\n\r\n")

            # send the new player the description of their current room
            mud.send_message(id, load_room(players[id]["room"]).get_description())

        # each of the possible commands is handled below. Try adding new
        # commands to the game!

        # 'help' command
        elif command == "help":

            with open('help/help.txt', 'r', encoding='utf-8') as f:
                for line in f:
                    mud.send_message(id, line, '\r')
                mud.send_message(id, '')

        # 'help' command
        elif command == "quit":

            # send the player back the list of possible commands
            mud.send_message(id, "Saving...")
            save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
            mud.disconnect_player(id)

        # 'help' command
        elif command == "save":

            # send the player back the list of possible commands
            mud.send_message(id, "Saving...")
            save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
            mud.send_message(id, "Save complete")

        # '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 cmd_handler.parse(id, command, params, rm, mud, players):

            pass

        # 'go' command
        elif command == "go":

            # store the exit name
            ex = params.lower()

            # store the player's current room
            rm = load_room(players[id]["room"])

            # if the specified exit is found in the room's exits list
            if ex in rm.get_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
                loaded = load_room(rm.get_exits()[ex])
                if loaded == None:
                    mud.send_message(id, "An invisible force prevents you from going in that direction.")
                    continue
                else:
                    players[id]["room"] = rm.get_exits()[ex]
                    rm = loaded

                # 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(rm.get_title()))

            # 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))


        # some other, unrecognised command
        else:
            # send back an 'unknown command' message
            mud.send_message(id, "Unknown command '{}'".format(command))
        prompt(id)