Search code examples
pythonpython-3.xalgorithmevent-driven

How do I make these channels visible dynamically with just joins and leaves?


Consider this MVCE:

import os
from time import sleep
from typing import List, Optional


class Member:
    def __init__(self, name: str):
        self.name: str = name


class VoiceChannel:
    def __init__(self, number: int):
        self.number: int = number
        self.members: List[Member] = []
        self.visible = True

    def is_empty(self) -> bool:
        return len(self.members) == 0

    def add_member(self, member: Member):
        self.members.append(member)

    def remove_member(self, member: Member):
        self.members.remove(member)


class System:
    def __init__(self):
        self.channels: List[VoiceChannel] = []
        for channel_number in range(1, 6):
            if channel_number == 1:
                self.channels.append(VoiceChannel(channel_number))
            else:
                _ = VoiceChannel(channel_number)
                _.visible = False
                self.channels.append(_)

    def get_room(self, number: int) -> Optional[VoiceChannel]:
        if number > 0:
            return self.channels[number - 1]

    def on_voice_state_update(
            self,
            member: Member,
            before: Optional[VoiceChannel],
            after: Optional[VoiceChannel]
    ):
        def join():
            pass

        def leave():
            pass

        if after in self.channels:
            join()

        if before in self.channels:
            leave()

    def join(self, member: Member, channel_number):
        self.channels[channel_number - 1].add_member(member)
        self.update_screen()
        self.on_voice_state_update(member, None, self.channels[channel_number])
        self.update_screen()

    def leave(self, member: Member):
        on_leave_channel = None
        for channel in self.channels:
            if member in channel.members:
                on_leave_channel = channel
                channel.remove_member(member)
                self.update_screen()
                break

        if on_leave_channel:
            self.on_voice_state_update(member, on_leave_channel, None)

    def move(self, member: Member, channel_number: int):
        for current_channel in self.channels:
            if member in current_channel.members:
                current_channel.remove_member(member)
                self.update_screen()
                break
        channel = self.get_room(channel_number)
        channel.add_member(member)
        self.update_screen()

    def update_screen(self):
        _ = os.system("cls")
        print("\n> Debate Rooms")
        for channel in self.channels:
            if channel.visible:
                print(f"     Debate {channel.number}")
                if not channel.is_empty():
                    for member in channel.members:
                        print(f"         {member.name}")
                else:
                    print(f" ")

        sleep(1.5)


if __name__ == "__main__":
    system = System()

    # Initialize Members
    john = Member("John")
    jane = Member("Jane")
    abby = Member("Abby")
    jess = Member("Jess")
    hope = Member("Hope")
    kate = Member("Kate")
    liam = Member("Liam")
    noah = Member("Noah")
    jack = Member("Jack")
    luke = Member("Luke")

    # Prime Members
    system.join(john, 1)  # Show Debate 2.
    system.leave(john)  # Hide Debate 2.
    system.join(john, 1)  # Show Debate 2.
    system.join(jane, 1)  # Keep Showing Debate 2.
    system.leave(john)   # Keep Showing Debate 2.
    system.leave(jane)  # Hide Debate 2.
    system.join(john, 1)  # Show Debate 2.
    system.join(jane, 2)  # Show Debate 3.
    system.join(abby, 3)  # Show Debate 4.
    system.leave(john)   # Hide Debate 4 since 1 is available.
    system.move(jane, 1)  # Keep Debate 4 hidden, and Debate 2 shown.

I'm trying to make these channels visible and invisible per this requirement:

  1. There's always exactly one empty channel visible.
  2. Users can join or leave visible channels [a channel is empty if there are no users].
  3. If a user joins the (single visible) empty channel, make the first available empty channel visible [to maintain (1)].
  4. If a user leaving results in an extra empty channel, only show the first empty channel [again, to maintain (1)]

How do I implement this using just the join() and leave() functions in on_voice_state_update()? I have been trying for a while and can't seem to figure it out.


Solution

  • I would make a function that assesses your condition 1. and acts accordingly if it isn't met. This function could work on the current state instead of having to know which member was removed/added to a room:

        def ensure_one_empty_room(self):
            empty_visible_channels = \
                [channel for channel in self.channels if channel.is_empty() and channel.visible]
    
            if len(empty_visible_channels)==0:
                first_empty_channel = next(channel for channel in self.channels if channel.is_empty())
                if first_empty_channel: 
                    first_empty_channel.visible=True
    
            elif len(empty_visible_channels)>1:
                for channel in empty_visible_channels[1:]:
                    channel.visible=False
    

    The full System class would then become:

    class System:
        def __init__(self):
            self.channels = [VoiceChannel(i) for i in range(1, 6)]
            self.ensure_one_empty_room()
            self.update_screen()
    
        def get_room(self, number: int) -> Optional[VoiceChannel]:
            if number > 0:
                return self.channels[number - 1]
    
        def ensure_one_empty_room(self):
            empty_visible_channels = \
                [channel for channel in self.channels if channel.is_empty() and channel.visible]
    
            if len(empty_visible_channels)==0:
                first_empty_channel = next(channel for channel in self.channels if channel.is_empty())
                if first_empty_channel: 
                    first_empty_channel.visible=True
    
            elif len(empty_visible_channels)>1:
                for channel in empty_visible_channels[1:]:
                    channel.visible=False
    
        def on_member_change(self):
            self.ensure_one_empty_room()
            self.update_screen()
    
        def joined_channels(self, member):
            return (channel for channel in self.channels if (member in channel.members))
    
        def join(self, member: Member, channel_number):
            self.get_room(channel_number).add_member(member)
            self.on_member_change()
    
        def leave(self, member: Member):
            for channel in self.joined_channels(member):
                channel.remove_member(member)
            self.on_member_change()
    
        def move(self, member: Member, channel_number: int):
            for channel in self.joined_channels(member):
                channel.remove_member(member)
            self.get_room(channel_number).add_member(member)
            self.on_member_change()
    
        def update_screen(self):
            os.system("cls")
            print("\n> Debate Rooms")
            for channel in self.channels:
                if channel.visible:
                    print(f"     Debate {channel.number}")
                    if not channel.is_empty():
                        for member in channel.members:
                            print(f"         {member.name}")
                    else:
                        print(f" ")
    
            sleep(1.5)
    

    note:

    • in your join() function, there is no check whether a member is already part of a channel, so he can join more than one channel. Therefore I changed to other functions (move and leave) to take this into account
    • I incorporated some examples of the nice list comprehension features in Python, just for illustration purposes