Python实现多人在线游戏

4
我写了一个相当简单的基于终端的扑克游戏(ascii艺术),目前它是多人游戏,但基本上你需要在一台电脑上进行轮流操作。是否有一种简单的方法,让两个人可以从不同的机器上连接并访问相同的游戏,同时一起玩?它不需要很花哨,也不需要图形化界面,只要我们有终端访问即可。
我不确定如何做到这一点或者它是否可行,但只是想学习和探索一些选项。
2个回答

13

这是一个非常模糊的问题,但我可以给你一些笼统的答案。

首先,您需要设计一个简单的协议。一个非常简单的基于行的协议应该可以正常工作:UTF-8文本,换行符分隔消息,空格分隔参数。例如,您可以有这些客户端->服务器消息:

JOIN name
SAY message with spaces
FOLD
RAISE amount
# ...

...以及这些服务器-客户端消息:

OK
ERROR error message
JOINED player name with spaces
LEFT player
SAID player message with spaces
NEWHAND player player player player…
DEALT player face suit
ANTED player amount
CHECKED player
# ...

像这样的协议非常方便,因为您可以使用 telnetnc 手动输入它,因此您甚至不需要客户端进行测试。

现在,您需要构建实现该协议的服务器,并将游戏逻辑构建到服务器中。

在这里,一个线程服务器可能是最简单的方法。然后主线程启动一个游戏线程,它大部分时间都在阻塞等待玩家行动的 Condition 上。它还会在 accept 上阻塞,为每个连接启动一个新的客户端线程,该线程大部分时间都在阻塞等待 for line in self.sock.makefile():。在客户端对象内添加一个Lock以允许其他线程安全地发送消息。然后,您只需要一个带有锁定的客户端对象集合,就完成了。

由于我已经有一个类似设计的聊天服务器,所以让我从中提取出一些部分来给你提供一个骨架。

首先,这是整个主线程:

lock = threading.Lock()
clients = []
game = Game()

ssock = socket.socket()
ssock.bind(('', 12345))
ssock.listen(5)
while True:
    sock, addr = ssock.accept()
    with lock:
        clients.append(Client(addr, sock, len(clients))

Client 对象是一个标准的调度器:

class Client(object):
    def __init__(self, addr, sock, number):
        self.sock = sock
        self.name = '<{}> (not logged in)'.format(addr)
        self.number = number
        self.lock = threading.Lock()
        self.thread = threading.Thread(target=self.serve)
        self.thread.start()

    def send(self, msg):
        with self.lock:
            self.sock.send(msg)

    def run(self):
        for line in self.sock.makefile():
            args = line.rstrip().split()
            cmd = args.pop().upper()
            method = getattr(self, 'do_{}'.format(cmd), None)
            if method is none:
                self.write('ERROR unknown command {}\n'.format(cmd))
            else:
                try:
                    method(*args)
                except Exception as e:
                    self.send('ERROR in {}: {}\n'.format(cmd, e))
                else:
                    self.send('OK\n')

您可能还需要一个broadcast函数:

def broadcast(msg):
    with lock:
        for client in clients:
            client.send(msg)

然后,您在Client上编写每个命令的方法。 基本上,您在菜单代码中有的每个elif response == 'FOO'都会变成一个do_FOO方法,而每个print都会变成一个broadcast,然后就这样了。 我稍后会展示一个更复杂的例子,但这是大部分方法的外观:

def do_SAY(self, *msg):
    broadcast('SAID {} {}'.format(self.number, ' '.join(msg)))

最后,还有一个 Game 对象。它在自己的线程上运行,就像每个 Client 一样。大部分时间,它的 run 方法与您的顺序、非网络游戏的逻辑相同。当然,您必须调用 broadcast 而不是 print,但这很容易。唯一棘手的部分是需要一些同步。

例如,在开始新手牌之前,您必须复制玩家列表(和可能的其他相关游戏状态),以便其他线程可以修改它而不影响当前游戏,并且还需要等待足够的玩家,以免只有1个人玩自己的手牌。所以:

def new_hand(self):
    with self.condition:
        while len(self.players) < 2:
            self.condition.wait()
        players = self.players
    # all your existing sequential logic

你需要为客户端添加一个join方法,以便他们可以从自己的线程中调用:

def join(self, player):
    with self.condition:
        self.players.append(self)
        self.condition.notify()

所以,在 Client 对象中:

def do_JOIN(self, name):
    self.name = name
    game.join(self)
    broadcast('JOINED {} {}'.format(self.number, self.name)

让我们尽可能地使等待下注变得复杂,只是为了看看即使在最坏的情况下,它是多么容易。如果你想提前下注,你可以这样做。每个人都可以看到你的赌注,如果情况发生变化,你就必须履行承诺(例如,如果你跟注,然后你前面的人加注,你就必须跟上他的新赌注)。因此,我们要做的是:

def wait_for_bets(self, bettor):
    with self.condition:
        while self.bets[self.bettor] is None:
            self.condition.wait()
        bettor, bet = self.bettor, self.bets[self.bettor]
        self.bets[self.bettor] = None
    # handle the bet

以下是Client提交赌注的方法:

def bet(self, player, bet):
    with self.condition:
        self.bets[player] = bet
        self.condition.notify()
例如,在Client中:
def do_FOLD(self):
    game.bet(self, 'fold')

显然有很多代码需要编写。但重点是除了已经显示的内容或已存在于现有游戏中的内容之外,没有任何复杂的东西。


2
哇,这个答案做得很好。可惜你只有一个赞! - Matthew Sainsbury

1
您需要托管某种类型的服务器,并编写一个处理包含某些数据类型的请求的程序,并将其发送回客户端的程序。由于这不是实时游戏,您不需要太多涉及TC/IP、UDP或任何其他东西,简单的HTTP请求可能就可以了。
事实上,您甚至可以使用一个名为Scoreoid的免费服务。我正在为我的游戏使用它。它专为高分榜设计,但它可能适合您的需求。它非常容易使用。由于API完全基于URL工作,因此您可以只使用标准库的urllib模块。这可能是开始进行此类操作的非常好的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接