Объектно-ориентированная игра в покер

Краткое контекстное описание: это игра в покер. Позже я планирую добавить «карточки» и «горшок» -объект, а затем изучить программирование, управляемое событиями, чтобы я мог добавить графический интерфейс. По сути, место игрока в переменной player-объекта игры определяет его место.

Мои основные вопросы касаются размещения методов и данных каждого объекта. Видите ли вы, ребята, какие-нибудь проблемы в будущем, например, когда ставки будут методом игрока, а не методом раунда? Или что не существует метода раунда для превращения игроков в неактивные, кроме как в конце раунда, который делает неактивными всех игроков?

Помимо этого, приветствуется практически любая обратная связь, если, конечно, она подкреплена разумным объяснением. Я просто пытаюсь выучить как можно больше трюков и «думать о будущем».

Код:

import random
import copy
import importlib

def limit(num, minimum=1, maximum=255):
  """Limits input 'num' between minimum and maximum values.
  Default minimum value is 1 and maximum value is 255."""
  return max(min(num, maximum), minimum)

#A player class - contains name, amount of cash and active-states
class player:

    def __init__(self,name,initMoney):
        self.cash = initMoney
        self.chipsInPlay = 0 #How much has this player betted?
        self.name = name

        #Data Related To Active-States
        self.activeGame = 1 #Does it exist at all? Reasons for non-existance: No cash, no chips and no possibility of re-joining
        self.activeRound = 1 #Does it exist in this round? Reasons for 0: No cash, no chips, folding
        self.activeTurn = 0 #Is it active this turn? Reasons for 0: Another player is active

    #If the player has no money and no stake in the pot, they're out of the game
    def checkIfOut(self):
        if self.cash == 0 and self.chipsInPlay == 0:
            self.activeRound = 0
            self.activeTurn = 0
            self.activeGame = 0

    #Checks if the player has enough money to bet the blind and bets - doesn't check activeTurn - Doesn't change: chipsInPlay,activeTurn
    def blindMoneyBet(self,blind):
        if blind<self.cash or blind == self.cash:
            self.cash = self.cash - blind
            print(f"nPlayer: {self.name} just bet the blind of {blind}$nCurrent balance: {self.cash}$nCurrent money in pot: {self.chipsInPlay}$n")
            return
        else:
            print("blind()-Player: Imagine an all-in function here")

    #Checks if the player has enough to bet and bets money for the player, the money goes into the pot
    #Checks: If allActiveStates = 1 - Changes: activeTurn = 0*,chipsInPlay = +bet - *: unless endTurn = 0
    def regularMoneyBet(self,moneyChange,min =0,endTurn = 1):
        if self.activeGame == 1 and self.activeRound == 1 and self.activeTurn == 1:
            betLimit = limit(moneyChange,min,self.cash)
            if moneyChange == betLimit: #He cannot bet more, than he has
                self.cash = self.cash - moneyChange
                self.chipsInPlay = self.chipsInPlay + moneyChange
                print(f"nPlayer: {self.name} just bet {moneyChange}$nCurrent balance: {self.cash}$nCurrent money in pot: {self.chipsInPlay}$n")
                if endTurn == 1:
                    self.activeTurn = 0
            elif moneyChange != betLimit:
                print(f"{self.name} tried to bet: {moneyChange}$, while he only has: {self.cash}$")
        elif self.activeGame != 0 or self.activeRound != 0 or self.activeTurn != 0:
            print(f"Player: {self.name} is not active in the following ways:nGame: {self.activeGame}nRound: {self.activeRound}nTurn: {self.activeTurn}")

    #Turns activeRound = 0
    def fold(self):
        self.activeRound = 0
        print(f"{self.name} just folded")

    def __str__(self):
            return f"nName: {self.name} nCash left: {self.cash}nMoney in pot: {self.chipsInPlay}nGame active = {self.activeGame}nRound active = {self.activeRound}nTurn active = {self.activeTurn}"


#Contains previous turns
class gameRound:
    def __init__(self,players,startingPlayer = 0,bigBlind = 0, smallBlind = 0, tableSize = 5):

        #Data related to players in the round
        self.startingPlayer = startingPlayer #Given by the game object at initialization (Object)
        self.roundPlayers = players #Players who're still in play in the round (list with player objects)
        self.playersActiveRound = len(players) #Amount of players in the game (integer)

        #Data related to turns
        self.turns = []
        self.lastTurnType = 0 #For keeping tack of possible actions (Integer)
        self.latestBet = 0 #The last bet, that was made in this round - To decide how much to raise or check (integer)
        self.turnNumber = 0 #For keeping track of which turn number, this is (integer)

        #Data related to who is active this turn
        self.activeThisTurn = 0 #Which player is active (object)
        self.activeThisTurnNumber = 0 #What number in the list of active players is the current active player (integer)
        self.playersActiveTurn = 0 #How many players have self.activeTurn = 1 - used for debugging (integer)

        #Data related to initial setup
        self.bigBlind = bigBlind #The bet that the first activeThisTurn has to bet (integer)
        self.smallBlind = smallBlind #The bet that roundPlayers[activeThisTurnNumber -1] has to bet i.e Player to the left of BBL. (integer)
        self.saveTurn() #Saves the initial player data at turn X (list with player objects)
        self.tableSize = tableSize

    #Debug methods below

    #Finds how many players are active - integer, not the actual player objects
    def findPlayersActiveTurn(self):
        g = 0
        for x in self.roundPlayers:
            if x.activeTurn == 1:
                g += g
        self.playersActiveTurn = g

    #Sets the person who is active this turn, and sets the previous active player as inactive (turn)
    def setActiveTurn(self,playerName): #Startingplayer, which is the optional argument in the game object, which is passed down into playerName is by default 0
        if type(self.activeThisTurn) == player:
            self.activeThisTurn.activeTurn = 0

        if playerName == 0: #If no name is given
            x = self.roundPlayers[random.randint(0, self.playersActiveRound - 1)]
            self.activeThisTurn = x
            self.findActiveNumber()
            x.activeTurn = 1
        elif playerName != 0: #If a name is given
            for x in self.roundPlayers:
                if x.name == playerName:
                    x.activeTurn = 1
                    self.activeThisTurn = x

    #Saves the current player data as a nested list in the self.turns list
    def saveTurn(self):
        z = [] #For storing playerdata
        for x in self.roundPlayers:
            y = copy.copy(x) #Makes a copy of the player objects
            z.append(y)

        self.turns.append(0) #Adds a new index
        self.turns[self.turnNumber] = z #Turns this index into z

    #Finds the current active player's number in the turn order
    def findActiveNumber(self):
        g= -1 #List indexes start at 0
        for x in self.roundPlayers:
            g = g +1
            if x == self.activeThisTurn:
                self.activeThisTurnNumber = g
                #Make a debug such that, if there are more actives this turn, it will say so

    #Selects the closest roundActive player to the right of the current activeTurnPlayer as the next activeTurn.
    def nextActive(self):
        self.findActiveNumber()

        y = (self.activeThisTurnNumber+1)%len(self.roundPlayers) #Goes one player to the right, modulo so it restarts at 0 if y +1 is out of bounds

        for x in range(y,len(self.roundPlayers)): #x in [y;self.playersActiveRound[
            h = x%len(self.roundPlayers)
            self.roundPlayers[h].checkIfOut()

            if self.roundPlayers[h].activeRound == 1 and self.roundPlayers[h] != self.activeThisTurn: #First activeRound player to the right of this current active player
                self.roundPlayers[h].activeTurn = 1
                self.activeThisTurn = self.roundPlayers[h]
                return() #Ends it
            else:
                print(f"nNo other active players than {self.activeThisTurn.name}")

    #Removes inactive players from the round list
    def removeInactiveRound(self):
        listOfActivePlayers = []
        for x in self.roundPlayers:
            x.checkIfOut()
            if x.activeRound == 1 and x.activeGame == 1:
                listOfActivePlayers.append(x)
        self.playersActiveRound = len(listOfActivePlayers)
        self.roundPlayers = listOfActivePlayers

    #Increments the turn by 1, changes the activeTurn player, removes inactive players and saves the player data to the turnlist
    def endTurn(self):
        self.turnNumber = self.turnNumber + 1
        self.nextActive()
        self.removeInactiveRound()
        self.saveTurn()

    def startingTurn(self):
        self.setActiveTurn(self.startingPlayer) #Starting player is provided by the game-object whenever a round is initialized
        self.activeThisTurn.blindMoneyBet(self.bigBlind) #Blind instead of moneybet, as there are no restrictions in terms of active status
        print(self.activeThisTurnNumber)
        self.roundPlayers[self.activeThisTurnNumber-1].blindMoneyBet(self.smallBlind) #This works because -1 = highest number, so if 0 -1 = -1 = highest index in the list


class game:
    def __init__(self,initPlayers,startingPlayer = 0,bigBlind = 25, smallBlind = 10):
        self.players = initPlayers
        self.updateValuesPlayersSum()
        self.playersCash = self.sumList[0] #How Much Money The Players Have excl. In Pot
        self.playersActiveGame = self.sumList[1] #How Many Players Are Active In The Game (int)
        self.chipsInPlay = self.sumList[2] #The Current Pot Size
        self.bigBlind = bigBlind
        self.smallBlind = smallBlind
        self.startingPlayer = startingPlayer #The initial starting player for this or the next round
        self.startRound() #Creates a gameRound object, chooses the initial starting player and makes the players bet BBL and SBL
        self.currentRound

    #Sums up the amount of cash held by all players and returns a float
    def cashPlayersSum(self,players):
        sum = 0
        for x in players:
            sum = x.cash + sum
        return sum

    #Sums the amount of active players and returns an integer
    def playersActiveGameSum(self,players):
        sum = 0
        for x in players:
            sum = x.activeGame + sum
        return sum

    #Sums the chips in play AKA the Pot and returns number
    def chipsInPlayPlayersSum(self,players):
        sum = 0
        for x in players:
            sum = x.chipsInPlay + sum
        return sum

    # Sums up all sum-values and adds them to a list
    def valuesPlayersSum(self,players):
        totalSum = []
        totalCash = self.cashPlayersSum(players)
        totalActivesGame = self.playersActiveGameSum(players)
        totalChipsInPlay = self.chipsInPlayPlayersSum(players)

        totalSum.append(totalCash)
        totalSum.append(totalActivesGame)
        totalSum.append(totalChipsInPlay)

        return totalSum

    #Updates the game's player-based sums
    def updateValuesPlayersSum(self):

        totalSum = self.valuesPlayersSum(self.players)

        self.sumList = totalSum
        self.playersCash = self.sumList[0]
        self.totalActivesGame = self.sumList[1]
        self.chipsInPlay = self.sumList[2]

    #Sets a person to be active in the round, and makes the first active player bet, and the player left to him on the list bet.
    def startRound(self):
        self.currentRound = gameRound(self.players,self.startingPlayer,self.bigBlind,self.smallBlind)

    def gameEndTurn(self):
        self.currentRound.endTurn()
    #Needs a function that continously allows for each active player to choose an action, unless all - 1 have folded, all have checked, or all have called the bet enough times to end round

    def __str__(self):
        return f"nPlayers in game : {str(self.players)} nActive players: {str(self.playersActiveGame)} nPot size: {str(self.chipsInPlay)} nCash on hand:  {str(self.playersCash)} "


def testNoob():
    player0 = player("player0",125)
    player1 = player("player1",125)
    player2 = player("player2",125)

    players = [player0,player1,player2]

    aGame = game(players)
    return aGame

2 ответа
2

Добро пожаловать в CodeExchange. Вот несколько способов улучшить ваш код:

Код стиля

Соглашения об именах

Пожалуйста, используйте PEP-8 соглашения об именах. Например, все имена классов должны начинаться с заглавной буквы:

game -> Game
player -> Player

В общем вроде как за ними следишь 🙂

Длинные линии

Не пишите слишком длинные строки. Из-за этого их трудно читать. Например:

return f"nPlayers in game : {str(self.players)} nActive players: {str(self.playersActiveGame)} nPot size: {str(self.chipsInPlay)} nCash on hand:  {str(self.playersCash)} "

Более читается как:

# By the way, there is no need to start the string representation of a class
# with an empty line. If what you want is to have an empty line before 
# the actual class string representation, that should be the caller's responsibility
return f"Players in game : {str(self.players)}n"
    + f"Active players: {str(self.playersActiveGame)}n"
    + f"Pot size: {str(self.chipsInPlay)}n"
    + f"Cash on hand:  {str(self.playersCash)}"

Особенно, PEP-8 рекомендует делать строки не длиннее 80 символов. Некоторые утверждают, что этого недостаточно (особенно если вы хотите давать переменным длинные значащие имена), но это уже намек. Например, строка:

return f"nName: {self.name} nCash left: {self.cash}nMoney in pot: {self.chipsInPlay}nGame active = {self.activeGame}nRound active = {self.activeRound}nTurn active = {self.activeTurn}"

вместе с отступом составляет 202 символа. Большинство людей не смогут уместить на экране такую ​​длинную строку (чтобы не сказать, что ее нелегко прочитать).

То же самое и с комментариями. Комментировать код — это действительно хорошая практика. Однако, если вы планируете иметь длинные комментарии, пишите их над утверждениями, а не рядом с ними. Например:

self.setActiveTurn(self.startingPlayer) #Starting player is provided by the game-object whenever a round is initialized

Более читабельный как таковой:

# Starting player is provided by the game-object whenever a round is 
# initialized
self.setActiveTurn(self.startingPlayer)

Единый стиль

Будьте последовательны в своем стиле кода.

Например, иногда вы оставляете пробелы между операторами, а иногда нет:

# Asignments
g= -1 #List indexes start at 0
g = g +1
self.turnNumber = self.turnNumber + 1
sum = x.chipsInPlay + sum
y = (self.activeThisTurnNumber+1)%len(self.roundPlayers)
...
# Functions/methods
def limit(num, minimum=1, maximum=255):
def __init__(self,name,initMoney):
def regularMoneyBet(self,moneyChange,min =0,endTurn = 1):

Более того, следуя PEP-8 рекомендации, вы всегда должны (за некоторыми исключениями) оставлять пробел между операторами и переменными, а также после запятых. Таким образом, предыдущие утверждения / объявления лучше было бы записать как:

# Asignments
g = -1 #List indexes start at 0
g = g + 1
self.turnNumber = self.turnNumber + 1
sum = x.chipsInPlay + sum
y = (self.activeThisTurnNumber + 1) % len(self.roundPlayers)
...
# Functions/methods
def limit(num, minimum=1, maximum=255):
def __init__(self, name, initMoney):
def regularMoneyBet(self, moneyChange, min=0, endTurn=1):

Строки документации

Это действительно хорошая практика — документировать свои методы / функции. Просто используйте строки документации так что ваши комментарии могут быть использованы для создания документации.

Например, если вы документируете такую ​​функцию:

# This is my documentation
def myFunction():
    pass

Тогда вы не сможете получить доступ к документации, если не читаете код:

>>> help(myFunction)
Help on function myFunction in module __main__:

myFunction()

Однако, если вы задокументируете это так:

def myFunction():
    '''This is my documentation'''
    pass

Потом:

>>> help()
Help on function myFunction in module __main__:

myFunction()
    This is my documentation

Вы можете узнать больше о строках документации здесь.

Дизайн

Теперь, что касается дизайна кода, вот несколько советов:

Родные типы существуют не просто так!

Булы

Player.activeGame всегда используется как переменная с двумя состояниями: 0 или 1. Это именно то, что bools предназначены для:

class Player:
    def __init__(self,name,initMoney):
        ...

        self.activeGame = True
        self.activeRound = True
        self.activeTurn = False

Забегая на шаг вперед, лучше называть логические значения глаголами:

class Player:
    def __init__(self,name,initMoney):
        ...

        # Some names that might be more meaningful
        self.isInGame = True
        self.isPlayingCurrentRound = True
        self.isTheirTurn = False

Теперь вы можете изменить свои условия, например:

if self.activeGame == 1 and self.activeRound == 1 and self.activeTurn == 1
    ...

просто:

if self.isInGame and self.isPlayingCurrentRound and self.isTheirTurn:
    ...

Если вы затем задаетесь вопросом, например, как получить общее количество игроков в игре, всегда есть решения проблем. Например:

# Instead of this
def playersActiveGameSum(self, players):
    sum = 0
    for x in players:
        sum = x.activeGame + sum
    return sum

# You could use a more idiomatic expression
 def playersActiveGameSum(self, players):
    return sum([1 for player in players if player.isInGame])

Диктовка

Если вы планируете получать в функции, казалось бы, разные данные, например здесь:

def valuesPlayersSum(self, players):
    totalSum = []
    totalCash = self.cashPlayersSum(players)
    totalActivesGame = self.playersActiveGameSum(players)
    totalChipsInPlay = self.chipsInPlayPlayersSum(players)

    totalSum.append(totalCash)
    totalSum.append(totalActivesGame)
    totalSum.append(totalChipsInPlay)

    return totalSum

Вы можете использовать dict, чтобы доступ к возвращаемым значениям имел больше семантики:

def valuesPlayersSum(self, players):
    playerStatistics = {}

    playerStatistics['totalCash'] = self.cashPlayersSum(players)
    playerStatistics['totalActivesGame'] = self.playersActiveGameSum(players)
    playerStatistics['totalChipsInPlay'] =  self.chipsInPlayPlayersSum(players)

    return totalSum

Таким образом, вы получите доступ к данным возвращенного значения как таковые:

statistics = self.valuesPlayersSum(players)
print(f'Total cash: {statistics['totalCash']}')
print(f'Total active players: {statistics['totalActivesGame']}')
print(f'Total chips in play: {statistics['totalChipsInPlay']}')

Еще лучше использовать поставляемые по умолчанию namedtuple в collections модуль:

from collections import namedtuple
PlayerStatistics = namedtuple('PlayerStatistics', 
    ['totalCash', 'totalActivesGame', 'totalChipsInPlay'])

...

def valuesPlayersSum(self, players):
    totalCash = self.cashPlayersSum(players)
    totalActivesGame = self.playersActiveGameSum(players)
    totalChipsInPlay =  self.chipsInPlayPlayersSum(players)

    return PlayerStatistics(totalCash, totalActivesGame, totalChipsInPlay)

...

statistics = self.valuesPlayersSum(players)
print(f'Total cash: {statistics.totalCash}')
print(f'Total active players: {statistics.totalActivesGame}')
print(f'Total chips in play: {statistics.totalChipsInPlay}')

Дублированный код

Постарайтесь следовать принципу DRY (Dont Repeat Yourself). Особенно, если вы собираетесь делать одно и то же дважды, один за другим.

Например, в Gameконструктора, вы выполняете следующие задания:

def __init__(self,initPlayers,startingPlayer = 0,bigBlind = 25, smallBlind = 10):
        ...
        self.updateValuesPlayersSum()
        self.playersCash = self.sumList[0]        # <- Here
        self.playersActiveGame = self.sumList[1]  # <- Here
        self.chipsInPlay = self.sumList[2]        # <- Here
        ...

Когда вы уже сделали их внутри Game#updateValuesPlayersSum:

def updateValuesPlayersSum(self):
        totalSum = self.valuesPlayersSum(self.players)

        self.sumList = totalSum
        self.playersCash = self.sumList[0]      # <- Here
        self.totalActivesGame = self.sumList[1] # <- Here
        self.chipsInPlay = self.sumList[2]      # <- Here

Другие комментарии

При отправке такой программы на рассмотрение будет проще, если вы предоставите исполняемый код. Таким образом, мы сможем увидеть, как вы планируете его запускать.

Предоставленный вами код, если он выполняется, не делает ничего, кроме инициализации переменных.

Думаю, для начала это уже некоторая обратная связь. Если вы хотите обновить свой код и предоставить исполняемый код в другом вопросе, я, вероятно, мог бы рассмотреть больше дизайна 🙂

    Есть куча. В произвольном порядке,

    • Рассмотрите возможность включения подсказок типа PEP484. Для limit(), вы можете использовать параметр универсального типа, чтобы обеспечить, чтобы входные и выходные данные были числами, а числовые типы входных и выходных данных (float, int и т. д.) совпадали.
    • class Player, Название дела.
    • init_money и так далее, lower_snake_case, для всех ваших переменных и имен методов.
    • Добавление к параметрам префикса init_ не помогает.
    • active_game и т. д. должно быть bool и нет int. Их проверка должна выглядеть так if self.active_game, нет if self.active_game == 1
    • blind<self.cash or blind == self.cash просто должно быть blind <= self.cash
    • Есть несколько таких, но self.cash = self.cash - blind должно быть self.cash -= blind
    • Ваше соглашение nnnn$ странно, и, по крайней мере, в североамериканской культуре чаще встречается $nnn. Более общий способ — вообще не добавлять символ валюты и вызывать locale.currency() вместо.
    • В соответствии foo: #some comment должно быть foo: # some comment
    • Вы, кажется, назначаете 0 чтобы указать, что ссылка на объект, т. е. startingPlayer, пустой». Его следует заменить на None и назначил Optional введите подсказку.
    • start_round() не следует вызывать из __init__. Основная игровая логика должна выполняться отдельным методом; __init__ только для инициализации. Продолжая эту мысль: updateValuesPlayersSum устанавливает новые переменные, которых нет в __init__, что нехорошо.

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *