[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] "Pyttle.net"?



Short update:

I'm posting a first draft version of "Pyttlebot" and I would just like to post it here before I scrap the thing. Thanks to RB[0] I was able to give consideration to the possibility of one not being able to host a game though I'm on the workings of finding out what to do about it. I'm leaving it running on irc.freenode.net #pyttle.net for the following 12 hours or so, with possible interruptions if problems occur.

As the next thing I'm gonna do is use a Pygame GUI library to create a minimalist IRC client that automatically connects to irc.freenode.net and joys #pyttle.net and supports some basic functionalities such as chatting and private messages. I'll worry about the bot and protocol again once that is done.

What needs to change in the bot:
* Instead of the user informing the bot "I can host" or "I can't host", the bot itself should attempt connection to the user's machine and verify if the user can host, unless the user specifies a lack of desire for hosting a game.
* Its code should look prettier than it looks now, seriously. If there's any hope for me to get people using it for their own games.
* It should respond to channel messages directed at it, rather than having the person wondering on how to use it.
* Accept registering of dedicated servers. The bot has registered dedicated servers, then it doesn't need to wait for a hosting-capable user to show up, but it should ask the dedicated server if there's a free game slot available before announcing the game to the players.

Alex, if you're interested in building the bot, could you check the code (or visit it and test its behavior) and get a new one in the workings? You can change the grammar / syntax of the bot, but make it easy to parse by the client.

I would say that if the bot accomplishes this much, and the client is able to make these requests to the bot and react to a match announcement by launching a game, then Milestone 1 has been achieved. =)

If anyone is interested in trying it out the bot is on #pyttle.net at irc.freenode.org. To get instructions from it, you need to enter a private conversation and say something random.

-Thiago

PS: In case anyone is wondering why trying a connection is necessary, I'm also posting a log from a conversation I had in #twisted.

On Mon, Sep 20, 2010 at 9:07 PM, RB[0] <roebros@xxxxxxxxx> wrote:
Everything except the actual code and the "RB[0]'s design docs" wiki page on here is outdated, but:

http://code.google.com/p/galaxymageredux/

Cheers, and good luck :)


On Mon, Sep 20, 2010 at 1:02 PM, Thiago Chaves <shundread@xxxxxxxxx> wrote:
Can you provide a link to your library, RB[0]? I googled Galaxymage but I keep bumping into galaxymage.org, and the address is cybersquatted from here.

I understand what you said about the routing issues, and that's quite true and I run into that problem quite often and shamefully enough, I haven't considered that.

I'm pushing on anyways and finishing a first draft of the first bot (to which I won't give that much more thought if someone else is willing to take the job) and then I'll get a minimalist client and network game going. But thanks a lot for the input. =)

-Thiago


On Mon, Sep 20, 2010 at 7:41 PM, RB[0] <roebros@xxxxxxxxx> wrote:
Hmm, perhaps I should explain better.
The users/hosts wouldn't interact on the master server, except to connect to game servers, start servers and possibly chat.
The game servers would be programmed by the games using them, so either one server = one game, or like in GMR, one game server hosts multiple games...


On Mon, Sep 20, 2010 at 11:39 AM, RB[0] <roebros@xxxxxxxxx> wrote:
Well, this is something we had an issue with in GMR.
Not everyone can "host" a game, if you have a router like mine is it is quite nearly impossible to create a server that others can connect to.
The way we planned to do it in GMR, was to have people create their own server, and register it with the master server.
The master server in turn would check to ensure the game server was really visible, then load up it's data (game, version, max users/games, password, etc.) and provide that to people wanting to use the service.

You could do this via irc, but I think a simple PB solution would be simpler and more extensible. But if you make this up and it takes off we'll definitely at least support it in GMR :)


On Mon, Sep 20, 2010 at 12:37 AM, Thiago Chaves <shundread@xxxxxxxxx> wrote:
Missing end of sentence here: a default bot could/should exist for use by developers with no interest in customizing it for their own game, or to be extended by developers who want to add other features specific to their game.


On Mon, Sep 20, 2010 at 8:11 AM, Thiago Chaves <shundread@xxxxxxxxx> wrote:
It's very cool to hear about two other people so soon after the initial post interested in getting this going. =)

For purposes of not commenting about how much I'd like to get a better name for the thing everytime I mention it, I'm gonna use "Pyttle.net" on the email, but leave here stated that I'm not suggesting this as a name for the system. =P

So, things I was thinking of:

0. Users have a collection of more than zero games that support getting started by Pyttle.net / having matches started by.
1. Chatting between users, emoting actions, registering and confirming of usernames is handled by the chosen protocol and the chosen protocol's servers.
2. The client connects automatically to the chat server(s) and joins channels according to the collection of Pyttle.net-capable games present in the user's machine. #fog-of-war-chess, #galaxymage, #ssof, for instance.
3. Each game/channel has it's own bot running in there, which deals with negotiation of matches, scorekeeping (if there's any interest in the game for that), messages-of-the-day, etc. A default bot could/should.
4. Once a match has been arranged, the bot informs all involved clients that the match is gonna start, who are the players and in which IP's they can be found.
5. Once a client has been informed of a match, it takes care of launching the game and it deals with the network connections and data on its own accord.
6. Once a match is over, if there's interest by the developers to have some scoreboard, the clients inform the results back to the bot, who logs it and whatever.

I'm totally open to negotiating/discarding/changing any/all of these. I want something like a game-agnostic "battle.net" to happen. =)

Opinions? What is missing? What could be added? What is poorly explained?

-Thiago


On Sun, Sep 19, 2010 at 9:25 PM, Alex Nordlund <deep.alexander@xxxxxxxxx> wrote:
On Sun, Sep 19, 2010 at 7:22 PM, RB[0] <roebros@xxxxxxxxx> wrote:
> I've been reading Twisted documentation and this sounds like a
> less-than-guru-level thing to build on top of the IRC protocol.

I'd like to contribute to this project!

I enjoy IRC and have been building bots that play games over IRC for a while.

---
//Alex







<shundread> How do I figure out what is the assigned IP address to a connection when I use a Twisted protocol? In a connected socket it would be socket.getsockname()
<_habnabit> It's some method on your protocol's transport.
<_habnabit> But I forget the name of it.
<shundread> Does the transport base provide the method or may it be a different thing for each protocol?
<shundread> Thanks for this much already, by the way
<_habnabit> No, it's a thing that all protocols have, regardless of the implementation.
<Jerub> .getHost() isn't it?
<_habnabit> That's how protocols communicate.
<Jerub> .getPeer() and .getHost() ?
<_habnabit> Sounds good.
<shundread> Cool, thanks
<Jerub> it returns an IPort i think
<spiv> IAddress, IIRC
<shundread> I got another question. Is it sane to use the result from transport.getHost() to try to guess whether I'm behind a firewall or not? (192.168.x.y or 10.x.y.z or 172.something)
<shundread> Or would that be the stupid way to do so?
<_habnabit> Yes, it would be a very stupid way.
<Jerub> shundread: that's an interesting question.
<Jerub> it's not entirely stupid, but it's fragile.
<Jerub> the question is actually, why do you need to know and what are you planning to do about it?
<Jerub> if it were, say, a bittorrent client and you wanted to throw up a little bubble during the installation to warn the user they need to configure a port forward, it's probably a neat idea!
<shundread> I'm getting an IRC bot done to mediate game negotiation. You talk to the bot about wanting to participate in a game, it keeps track of other people trying to do so. Once you have enough players for a certain kind of game, the people get warned that a game has been arranged
<shundread> The thing is that if we have a real-time game, then there's no hope for an IRC bot to route the data around
<shundread> On the client-side, I'd like to have the client "know" that he's behind a firewall or not
<shundread> So that when a person is entering the queues, the client announces "I can host" or "I can't host"
<shundread> I was hoping there would be a simpler way than having the bot trying to attempt a connection and waiting for connection successful or timeout to happen
<shundread> simpler -> I actually meant one that doesn't result in the bot having to wait
<_habnabit> If it takes a while for a game to be arranged, why don't you check that passively when people say they want to host?
<_habnabit> It's going to be the most reliable solution, anyway.
<shundread> _habnabit, if there's no other way, then definitely.
<_habnabit> Though it doesn't account for people who have port forwards set up.
<shundread> _habnabit, but if I can somehow get a reliable answer without attempting connection, then why not using the alternative?
<_habnabit> Well, you *can't*, so...
<shundread> _habnabit, alright, so attempting connection it is then. Thanks. =)
<shundread> Well, that actually gives me a 3rd question. The IP the bot should try to connect to is the client protocol transport's getHost, right?
<shundread> Or should I parse /whois USERNAME and get that IP address instead?
<shundread> nevermind, it looks ke it should be getHost
<shundread> *looks like
#!/usr/bin/env python

"""This is the first draft for a Pyttle.net bot. At its current stage, it only
supports peer-based 2-player games, and has no mediating of map/stage selection
or game options."""

# twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log

# system imports
import time, sys

import math

class MatchMaker(object):
    """Match Making logic."""
    def __init__(self):
        self.match_queue = [] # List of (username, able_to_host_match) tuples
    
    def request_match(self, new_username, can_host):
        print "{0} requests a new match. can_host = {1}".format(new_username,\
            can_host)
        for (u, h) in self.match_queue:
            if new_username == u:
                return ("info", new_username, "You are already in the queue "\
                    "for a random match.")
        
        # If this challenger is a host candidate, then look for people in the
        # queue who can't host a match first.
        if can_host == True:
            clients = [(u, h) for (u, h) in self.match_queue if h == False]
            if len(clients) > 0:
                client = clients[0][0]
                server = new_username
                self.match_queue.remove(clients[0])
                return ("match", server, client)
        
        # If this challenger can't host or can't find an opponent who can't
        # host, then we try to deliver a hosting-capable match.        
        hosts = [(u, h) for (u, h) in self.match_queue if h == True]
        if len(hosts) > 0:
            client = new_username
            server = hosts[0][0]
            self.match_queue.remove(hosts[0])
            return ("match", server, client)
        
        # If we can't find a hosting-capable match, that means we couldn't find
        # any proper match at the moment, add the person to the challenger's
        # queue.
        self.match_queue.append((new_username, can_host))
        return ("info", new_username, "You are now in the queue for a random "\
            "match.")
    
    def clear_match(self, username):
        # See if the username is really in the queue
        for (u, h) in self.match_queue:
            # If so, remove the username from the queue
            if u == username:
                self.match_queue.remove((u, h))
                return ("info", username, "You are no longer in the queue for"\
                    " a random match.")
        
        # Otherwise, tell the user that he/she was not in the queue
        return ("info", username, "You were not waiting for a random match.")

    def queue_status(self, username):
        if len(self.match_queue) == 0:
            return ["The match queue is empty."]
        
        message = ["There are {0} people in the match queue.".format(\
            len(self.match_queue))]
        
        found = False
        for (i, (u, h)) in enumerate(self.match_queue):
            if u == username:
                found = True
                if i == 0:
                    message.append("You are the 1st in the queue.")
                elif i == 1:
                    message.append("You are the 2nd in the queue.")
                elif i == 2:
                    message.append("You are the 3rd in the queue.")
                else:
                    message.append("You are the {0}th in the queue.".format(\
                        i+1))
                
                if h:
                    message.append("You are listed as a potential host.")
                else:
                    message.append("You are listed as unable to host.")
        
        if not found:
            message.append("You are not in the match queue.")
        
        return message

class PyttleBot(irc.IRCClient):
    """My first attempt at a 'Pyttle.net' bot."""
    
    nickname = "PyttleBot_v1"
        
    def connectionMade(self):
        irc.IRCClient.connectionMade(self)
        print "[connected at {0}]".format(time.asctime(\
            time.localtime(time.time())))

    def connectionLost(self, reason):
        irc.IRCClient.connectionLost(self, reason)
        print "[disconnected at {0}]".format(time.asctime(\
            time.localtime(time.time())))
    
    # callbacks for events

    def signedOn(self):
        """Called when bot has succesfully signed on to server."""
        self.join(self.factory.channel)

    def joined(self, channel):
        """This will get called when the bot joins the channel."""
        print "I have joined {0}".format(channel)

    def privmsg(self, user, channel, msg):
        """This will get called when the bot receives a message."""
        user = user.split('!', 1)[0]
        
        # Check to see if they're sending me a private message
        if channel == self.nickname:
            tokens = msg.split()
            action = tokens[0]
            arguments = tokens[1:]
            if action == "request_match":
                if len(arguments) > 0 and arguments[0] == "host":
                    result = self.factory.matchmaker.request_match(user, True)
                else:
                    result = self.factory.matchmaker.request_match(user, False)
                msgtype = result[0]
                msgargs = result[1:]
                if msgtype == "info":
                    self.msg(msgargs[0], msgargs[1])
                elif msgtype == "match":
                    (server, client) = msgargs
                    self.msg(server, "You will host a game against {0}".format(client))
                    self.msg(client, "{0} will host a game against you.".format(server))
                return
            elif action == "queue_status":
                message = self.factory.matchmaker.queue_status(user)
                for line in message:
                    self.msg(user, line)
            elif action == "clear_request":
                result = self.factory.matchmaker.clear_match(user)
                self.msg(user, result[2])
            else:
                self.help_user(user)
    
    def help_user(self, user):
        help_message = [\
            "Instructions:",\
            \
            "request_match [host]: request a match as a client, if the word "\
            "\"host\" is omitted. Otherwise request a match as a host. If "\
            "there are hosts in the queue, but no clients, you will be "\
            "matched as a client against one of them.",\
            \
            "clear_request: remove yourself from the match queue.",\
            \
            "queue_status: returns the status of the queue, who is in, who is"\
            " a host and who is a client."\
            ]
        for line in help_message:
            self.msg(user, line)

class PyttleBotFactory(protocol.ClientFactory):
    """A factory for LogBots.

    A new protocol instance will be created each time we connect to the server.
    """

    # the class of the protocol to build when new connection is made
    protocol = PyttleBot

    def __init__(self):
        self.channel = "pyttle.net"
        self.matchmaker = MatchMaker()

    def clientConnectionLost(self, connector, reason):
        """If we get disconnected, reconnect to server."""
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        print "connection failed:", reason
        reactor.stop()


if __name__ == '__main__':    
    # create factory protocol and application
    f = PyttleBotFactory()

    # connect factory to this host and port
    reactor.connectTCP("irc.freenode.net", 6667, f)
    
    # run bot
    reactor.run()