Aprenda a implementar o Banco Imobiliário em Python

Você provavelmente deve estar pensando que sou louco agora!

Aquele jogo destruiu jantares e tardes com sua família e ainda te traumatiza quando você ouve o nome.

Mas dessa vez é diferente…

Dessa vez a gente vai programar um Banco Imobiliário para podermos simular diferentes estratégias que podem ser empregadas no jogo enquanto você joga com sua família.

Vamos às instruções

Esse será um jogo de Banco Imobiliário com todas as dinâmicas simplificadas, assim não teremos de implementar totalmente tudo.

O jogo tem 20 propriedades, 4 jogadores (um com cada estratégia diferente que será explicada mais a frente).

Quando um jogador para em uma propriedade, ele tem 3 possíveis ações: comprar a propriedade caso ninguém tenha feito ainda e ele possua saldo OU pagar o aluguel ao dono OU não fazer nada.

A cada volta no tabuleiro, o jogador ganha 100 moedas, permitindo com que ele gaste da forma que entender melhor.

Um jogador que fica com saldo negativo perde o jogo, e não joga mais. Perde suas propriedades e portanto podem ser compradas por qualquer outro jogador.

O jogo acaba de 2 formas: por timeout (quando nenhum jogador saiu e ou ficou sem saldo) OU por eliminação (quando só restou um jogador).

#PartiuSimulação?

Nada é tão legal quanto uma simulação computadorizada, certo? (pelo menos é o que eu acho hehehe), então vamos fazer uma simulação com 4 players com diferentes comportamentos:

  • O jogador um é impulsivo – compra qualquer propriedade sobre a qual ele parar.
  • O jogador dois é exigente – compra qualquer propriedade, desde que o aluguel pago seja maior do que 50.
  • O jogador três é cauteloso – compra qualquer propriedade desde que ele tenha uma reserva de 80 saldo sobrando depois de realizada a compra.
  • O jogador quatro é aleatório – compra a propriedade que ele parar em cima com probabilidade de 50%.

Definindo tudo em código

Para definirmos tudo isso em código, precisamos definir quais serão nossas entidades principais! Como podemos presumir logo de cara, teremos 3 entidades principais:

  • O tabuleiro (classe Board) – onde serão armazenadas as propriedades existentes em cada posição e os players que estão no jogo
  • O player (classe Player)- onde serão armazenadas as informações de nome, saldo, se ele ainda está no jogo, se ele possui dinheiro o suficiente, e a ação de andar
  • A propriedade (classe RealEstate) – onde será armazenada as informações de nome, preço, valor do aluguel e quem é o dono

Logo, para cada classe deveremos ter um construtor que guarde e inicialize as propriedades do objeto com os parâmetros que queremos.

Pensando nisso, a classe do Tabuleiro que defini é esta:

class Board:
    def __init__(self):
        self.real_estate = []
        self.players = []
    
    def add_real_estate(self, real_estate):
        self.real_estate.append(real_estate)

    def add_players(self, players):
        self.players = players
    
    def shuffle_players(self):
        random.shuffle(self.players)

A de players:

class Player:
    def __init__(self, name, initial_balance=300.00):
        self.name = name
        self.balance = initial_balance
        self.is_in_the_game = True
        self.board_position = 0
        self.initial_balance = initial_balance

    def subtract_from_balance(self, amount):
        self.balance = self.balance - amount

    def owner_has_amount_available(self, needed_amount):
        return True if self.balance >= needed_amount else False

    def kick_player(self):
        if self.balance < 0:
            self.is_in_the_game = False
        return self.is_in_the_game

    def walk(self):
        thrown_dice = random.choice(range(1, 7))
        self.board_position += thrown_dice

        if self.board_position > 19:
            print(f"{self.name} RECEBEU PELA RODADA")
            self.board_position -= 19
            self.balance += self.initial_balance

        return self.board_position

E a de propriedades (casas):

class RealEstate:
    def __init__(self, re_name, buying_cost, rent_cost, owner=None):
        self.re_name = re_name
        self.buying_cost = buying_cost
        self.rent_cost = rent_cost
        self.owner = owner

    def buyable(self):
        if self.owner:
            return False
        return True

    def buy(self, possible_owner):
        if self.buyable():
            if possible_owner.owner_has_amount_available(self.buying_cost):
                possible_owner.subtract_from_balance(self.buying_cost)
                self.owner = possible_owner
                return True
        return False

    def pay_rent(self, player):
        if not self.buyable():
            print(f"{self.owner.name} recebeu aluguel")
            player.subtract_from_balance(self.rent_cost)
            self.owner.balance += self.rent_cost
            return True

    def bank_reposetion(self):
        self.owner = None
        return True

Legal, e como eu defino as estratégias que eu quero em cada jogador

Em orientação a objetos, temos um conceito fundamental chamado herança: a herança permite com que classes filhas herdem de uma classe mãe todos os comportamentos que já foram escritos para ela, permitindo com que você escreva novos métodos mantendo toda a estrutura da classe mãe apenas nela.

Ou seja, você estará reduzindo a duplicação de código e ao mesmo tempo executando métodos definidos em outra classe.

Um exemplo disso é o seguinte: Queremos implementar o player do Banco Imobiliário que compre toda casa que ele puder com seu saldo.

class AllInPlayer(Player):
    def is_buying_opportunity(self, real_estate):
        if (real_estate.owner is None and self.balance >= real_estate.buying_cost):
            real_estate.buy(self)
            return True
        return False

Como podemos ver, nessa classe definida acima, nós usamos um novo parâmetro na hora de definirmos o nome da classe, esse nome entre parênteses é a nossa classe mãe, de onde herdamos todos os métodos para utilizarmos nessa classe.

Nesse caso, criamos um método novo para checar se é uma oportunidade de compra, assim isolamos o método apenas para aquele player.

Nós poderíamos muito bem ter criado um método de buy e sobreescrito ele, mas para ser mais simples de demonstrar, resolvi usar essa abordagem.

Então como ficariam os outros players?

class RandomPlayer(Player):
    def is_buying_opportunity(self, real_estate):
        if (real_estate.owner is None and self.balance >= real_estate.buying_cost
            and random.choice([True, False])):
            real_estate.buy(self)
            return True
        
        return False


class CautiousPlayer(Player):
    def is_buying_opportunity(self, real_estate):
        if (real_estate.owner is None and (self.balance - 80) >= real_estate.buying_cost):
            real_estate.buy(self)
            return True
        
        return False


class ImpulsivePlayer(Player):
    def is_buying_opportunity(self, real_estate):
        if (real_estate.owner is None and self.balance >= real_estate.buying_cost):
            real_estate.buy(self)
            return True
        
        return False


class HighlyDemandingPlayer(Player):
    def is_buying_opportunity(self, real_estate):
        if (real_estate.owner is None and self.balance >= real_estate.buying_cost 
            and real_estate.rent_cost > 50):
            real_estate.buy(self)
            return True
        
        return False

No próximo post, demonstraremos como fazer a simulação dos players para sabermos qual a estratégia de melhor eficácia.

Autor: Vinicius Mesel

Ex-desenvolvedor de sistemas para bancos de investimentos. Trabalhou com mesas de investimentos e áreas de atendimento a grandes riquezas automatizando processos com Python. Também atuou como pesquisador de bioinformática no Instituto Butantan e hoje é CEO da RecrutaDev.

2 comentários em “Aprenda a implementar o Banco Imobiliário em Python”

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *