2013-06-19 106 views
3

我寫了一個非常簡單的基於終端的撲克遊戲(ascii art ftw),現在它是多人遊戲,但你基本上必須通過一臺計算機。 是否有一種簡單的(ish)方法可以讓兩個人可以從不同的機器連接並訪問同一個遊戲以同時玩耍?它不必是花式的,並且不需要是圖形化的,只要我們有終端訪問。通過python進行多人遊戲

我不確定如何做到這一點,或者如果它是實用的,但只想學習和探索一些選項。

回答

9

這是一個非常含糊的問題,但我可以給你一些含糊的答案。

首先,您需要設計一個簡單的協議。一個非常簡單的基於行的協議應該可以正常工作: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') 

顯然有一堆代碼寫。但重要的是,除了上面已經展示的東西或已經存在的遊戲之外沒有什麼複雜的。

+1

哇,這個答案很好的工作。可惜你只有一個贊成! –

0

您需要託管某種服務器,並編寫一個程序來處理包含特定類型數據的請求,並將其傳回客戶端。由於這不是一個真正的時間遊戲,你不需要用TC/IP,UDP或其他任何東西搞亂,簡單的HTTP請求可能會很好。

實際上,您甚至可以使用名爲Scoreoid的免費服務。我用它來玩我的遊戲。它專爲高分排行榜而設計,但它可能會滿足您的需求。這是非常容易使用。由於API完全使用URL,因此您可以使用標準庫的urllib模塊。這可能是開始處理這類事情的一種非常好的方法。