【问题标题】:Multiplayer Gaming via python通过 python 进行多人游戏
【发布时间】:2013-06-15 09:05:51
【问题描述】:

我写了一个非常简单的基于终端的扑克游戏(ascii art ftw),现在它是多人游戏,但你基本上必须通过一台计算机。 有没有一种简单的(ish)方法可以让两个人从不同的机器连接并访问同一个游戏同时一起玩?它不必花哨,也不必是图形的,只要我们有终端访问权限。

我不确定如何执行此操作或是否实用,但只是想学习和探索一些选项。

【问题讨论】:

    标签: python terminal multiplayer game-development


    【解决方案1】:

    您需要托管某种类型的服务器,并编写一个程序来处理包含某些类型数据的请求,并将其传回给客户端。由于这不是实时游戏,因此您无需过多地使用 TC/IP、UDP 或任何东西,简单的 HTTP 请求可能就可以了。

    事实上,您甚至可以使用名为Scoreoid 的免费服务。我将它用于我的游戏。它专为高分排行榜而设计,但它可能会满足您的需求。它非常易于使用。由于 API 完全依赖于 URL,因此您可以只使用标准库的 urllib 模块。这可能是开始这类事情的一个很好的方法。

    【讨论】:

      【解决方案2】:

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

      首先,您需要设计一个简单的协议。一个非常简单的基于行的协议应该可以正常工作: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')
      

      显然有一堆代码要写。但关键是,除了上面已经显示的内容或现有游戏中的内容之外,没有什么复杂的。

      【讨论】:

      • 哇,这个答案做得很好。可惜你只有一个赞成票!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-26
      • 2014-09-20
      • 1970-01-01
      • 1970-01-01
      • 2011-01-06
      • 1970-01-01
      相关资源
      最近更新 更多