def showChoice(self,
                   ch_idx,
                   game_no=None,
                   round_no=None,
                   rounds_tot=None,
                   won_sf=None,
                   drew_sf=None,
                   lost_sf=None):
        """Show player's choice on NeoPixels or display.
           The display also has game and round info and win/draw summary.
           This removes all the graphical objects and re-adds them which
           causes visible flickering during an update - this could be
           improved by only replacing / updating what needs changing.
        """
        if self.disp is None:
            self.pix.fill(BLACK)
            self.pix[self.choiceToPixIdx(ch_idx)] = CHOICE_COL[ch_idx]
            return

        self.emptyGroup(self.disp_group)
        # Would be slightly better to create this Group once and re-use it
        round_choice_group = Group(max_size=3)

        if round_no is not None:
            title_dob = Label(self.font,
                              text="Game {:d}  Round {:d}/{:d}".format(
                                  game_no, round_no, rounds_tot),
                              scale=2,
                              color=TITLE_TXT_COL_FG)
            title_dob.x = round(
                (self.width - len(title_dob.text) * 2 * self.font_width) // 2)
            title_dob.y = round(self.font_height // 2)
            round_choice_group.append(title_dob)

        if won_sf is not None:
            gamesum_dob = Label(self.font,
                                text="Won {:d} Drew {:d} Lost {:d}".format(
                                    won_sf, drew_sf, lost_sf),
                                scale=2,
                                color=TITLE_TXT_COL_FG)
            gamesum_dob.x = round(
                (self.width - len(gamesum_dob.text) * 2 * self.font_width) //
                2)
            gamesum_dob.y = round(self.height - 2 * self.font_height // 2)
            round_choice_group.append(gamesum_dob)

        s_group = Group(scale=3, max_size=1)
        s_group.x = 32
        s_group.y = (self.height - 3 * self.sprite_size) // 2
        s_group.append(self.sprites[ch_idx])
        round_choice_group.append(s_group)

        self.showGroup(round_choice_group)
    def showChoice(self,
                   ch_idx,
                   game_no=None,
                   round_no=None,
                   rounds_tot=None,
                   won_sf=None,
                   drew_sf=None,
                   lost_sf=None):
        """TODO DOC"""
        if self.disp is None:
            self.pix.fill(BLACK)
            self.pix[self.choiceToPixIdx(ch_idx)] = CHOICE_COL[ch_idx]
            return

        self.emptyGroup(self.disp_group)
        ### Would be slightly better to create this Group once and re-use it
        round_choice_group = Group(max_size=3)

        if round_no is not None:
            title_dob = Label(self.font,
                              text="Game {:d}  Round {:d}/{:d}".format(
                                  game_no, round_no, rounds_tot),
                              scale=2,
                              color=TITLE_TXT_COL_FG)
            title_dob.x = round(
                (self.width - len(title_dob.text) * 2 * self.font_width) // 2)
            title_dob.y = round(self.font_height // 2)
            round_choice_group.append(title_dob)

        if won_sf is not None:
            gamesum_dob = Label(self.font,
                                text="Won {:d} Drew {:d} Lost {:d}".format(
                                    won_sf, drew_sf, lost_sf),
                                scale=2,
                                color=TITLE_TXT_COL_FG)
            gamesum_dob.x = round(
                (self.width - len(gamesum_dob.text) * 2 * self.font_width) //
                2)
            gamesum_dob.y = round(self.height - 2 * self.font_height // 2)
            round_choice_group.append(gamesum_dob)

        s_group = Group(scale=3, max_size=1)
        s_group.x = 32
        s_group.y = (self.height - 3 * self.sprite_size) // 2
        s_group.append(self.sprites[ch_idx])
        round_choice_group.append(s_group)

        self.showGroup(round_choice_group)
    def showPlayerVPlayerScreen(self, me_name, op_name, my_ch_idx, op_ch_idx,
                                result, summary, win, draw, void):
        # pylint: disable=too-many-locals,too-many-branches,too-many-statements
        """Display a win, draw, lose or error message."""
        self.fadeUpDown("down")
        self.emptyGroup(self.disp_group)

        if void:
            error_tot = 3
            error_group = Group(max_size=error_tot + 1)
            # Opponent's name helps pinpoint the error
            op_dob = Label(self.font,
                           text=op_name,
                           scale=2,
                           color=OPP_NAME_COL_FG)
            op_dob.x = 40
            op_dob.y = self.font_height
            error_group.append(op_dob)
            self.showGroup(error_group)
            self.fadeUpDown("up", duration=0.4)
            if result is not None:
                self.sample.play(result)
            font_scale = 2
            # Attempting to put the three pieces of "Error!" text on screen
            # synchronised with the sound sample repeating the word
            for idx in range(error_tot):
                error_dob = Label(self.font,
                                  text="Error!",
                                  scale=font_scale,
                                  color=ERROR_COL_FG)
                error_dob.x = 40
                error_dob.y = 60 + idx * 60
                error_group.append(error_dob)
                time.sleep(0.5)  # Small attempt to synchronise audio with text
                font_scale += 1

        else:
            # Would be slightly better to create this Group once and re-use it
            pvp_group = Group(max_size=3)

            # Add player's name and sprite just off left side of screen
            # and opponent's just off right
            player_detail = [[
                me_name, self.sprites[my_ch_idx], -16 - 3 * self.sprite_size,
                PLAYER_NAME_COL_FG, PLAYER_NAME_COL_BG
            ],
                             [
                                 op_name, self.opp_sprites[op_ch_idx],
                                 16 + self.width, OPP_NAME_COL_FG,
                                 OPP_NAME_COL_BG
                             ]]
            idx_lr = [0, 1]  # index for left and right sprite
            if win:
                player_detail.reverse()  # this player is winner so put last
                idx_lr.reverse()

            # Add some whitespace around winner's name
            player_detail[1][0] = " " + player_detail[1][0] + " "

            for (name, sprite, start_x, fg, bg) in player_detail:
                s_group = Group(scale=2,
                                max_size=2)  # Audio is choppy at scale=3
                s_group.x = start_x
                s_group.y = (self.height - 2 *
                             (self.sprite_size + self.font_height)) // 2

                s_group.append(sprite)
                p_name_dob = Label(
                    self.font,
                    text=name,
                    scale=1,  # This is scaled by the group
                    color=fg,
                    background_color=bg)
                # Centre text below sprite - values are * Group scale
                p_name_dob.x = (self.sprite_size -
                                len(name) * self.font_width) // 2
                p_name_dob.y = self.sprite_size + 4
                s_group.append(p_name_dob)

                pvp_group.append(s_group)

            if draw:
                sum_text = "Draw"
            elif win:
                sum_text = "You win"
            else:
                sum_text = "You lose"
            # Text starts invisible (BLACK) and color is later changed
            summary_dob = Label(self.font, text=sum_text, scale=3, color=BLACK)
            summary_dob.x = round(
                (self.width - 3 * self.font_width * len(sum_text)) / 2)
            summary_dob.y = round(self.height - (3 * self.font_height / 2))
            pvp_group.append(summary_dob)

            self.showGroup(pvp_group)
            self.fadeUpDown("up", duration=0.4)

            # Start audio half way through animations
            if draw:
                # Move sprites onto the screen leaving them at either side
                for idx in range(16):
                    pvp_group[idx_lr[0]].x += 6
                    pvp_group[idx_lr[1]].x -= 6
                    if idx == 8 and result is not None:
                        self.sample.play(result)
                    time.sleep(0.2)
            else:
                # Move sprites together, winning sprite overlaps and covers loser
                for idx in range(16):
                    pvp_group[idx_lr[0]].x += 10
                    pvp_group[idx_lr[1]].x -= 10
                    if idx == 8 and result is not None:
                        self.sample.play(result)
                    time.sleep(0.2)

            self.sample.wait()  # Wait for first sample to finish

            if summary is not None:
                self.sample.play(summary)

            # Flash colours for win, fad up to blue for rest
            if not draw and win:
                colours = [YELLOW_COL, ORANGE_COL, RED_COL] * 5
            else:
                colours = [DRAWLOSE_COL * sc // 15 for sc in range(1, 15 + 1)]
            for col in colours:
                summary_dob.color = col
                time.sleep(0.120)

        self.sample.wait()  # Ensure second sample has completed
    def introductionScreen(self):
        # pylint: disable=too-many-locals,too-many-branches,too-many-statements
        """Introduction screen."""
        if self.disp is not None:
            self.emptyGroup(self.disp_group)
            intro_group = Group(max_size=7)
            welcometo_dob = Label(self.font,
                                  text="Welcome To",
                                  scale=3,
                                  color=IWELCOME_COL_FG)
            welcometo_dob.x = (self.width - 10 * 3 * self.font_width) // 2
            # Y pos on screen looks lower than I would expect
            welcometo_dob.y = 3 * self.font_height // 2
            intro_group.append(welcometo_dob)

            extra_space = 8
            spacing = 3 * self.sprite_size + extra_space
            y_adj = (-6, -2, -2)
            for idx, sprite in enumerate(self.sprites):
                s_group = Group(scale=3, max_size=1)
                s_group.x = -96
                s_group.y = round((self.height - 1.5 * self.sprite_size) / 2 +
                                  (idx - 1) * spacing) + y_adj[idx]
                s_group.append(sprite)
                intro_group.append(s_group)

            arena_dob = Label(self.font,
                              text="Arena",
                              scale=3,
                              color=IWELCOME_COL_FG)
            arena_dob.x = (self.width - 5 * 3 * self.font_width) // 2
            arena_dob.y = self.height - 3 * self.font_height // 2
            intro_group.append(arena_dob)

            self.showGroup(intro_group)

        # The color modification here is fragile as it only works
        # if the text colour is blue, i.e. data is in lsb only
        self.sample.play("welcome-to")
        while self.sample.playing():
            if self.disp is not None and intro_group[0].color < WELCOME_COL_FG:
                intro_group[0].color += 0x10
                time.sleep(0.120)

        onscreen_x_pos = 96

        # Move each sprite onto the screen while saying its name with wav file
        anims = (("rock", 10, 1, 0.050), ("paper", 11, 2, 0.050),
                 ("scissors", 7, 3, 0.050))
        for idx, (audio_name, x_shift, grp_idx, delay_s) in enumerate(anims):
            if self.disp is None:
                self.showChoice(idx)  # Use for NeoPixels
            self.sample.play(audio_name)
            # Audio needs to be long enough to finish movement
            while self.sample.playing():
                if self.disp is not None:
                    if intro_group[grp_idx].x < onscreen_x_pos:
                        intro_group[grp_idx].x += x_shift
                        time.sleep(delay_s)

        # Set NeoPixels back to black
        if self.disp is None:
            self.pix.fill(BLACK)

        self.sample.play("arena")
        while self.sample.playing():
            if self.disp is not None and intro_group[4].color < WELCOME_COL_FG:
                intro_group[4].color += 0x10
                time.sleep(0.060)

        # Button Guide for those with a display
        if self.disp is not None:
            left_dob = Label(self.font,
                             text="< Select    ",
                             scale=2,
                             color=INFO_COL_FG,
                             background_color=INFO_COL_BG)
            left_width = len(left_dob.text) * 2 * self.font_width
            left_dob.x = -left_width
            left_dob.y = self.button_y_pos
            intro_group.append(left_dob)

            right_dob = Label(self.font,
                              text=" Transmit >",
                              scale=2,
                              color=INFO_COL_FG,
                              background_color=INFO_COL_BG)
            right_width = len(right_dob.text) * 2 * self.font_width
            right_dob.x = self.width
            right_dob.y = self.button_y_pos
            intro_group.append(right_dob)

            # Move left button text onto screen, then right
            steps = 20
            for x_pos in [
                    left_dob.x + round(left_width * x / steps)
                    for x in range(1, steps + 1)
            ]:
                left_dob.x = x_pos
                time.sleep(0.06)

            for x_pos in [
                    right_dob.x - round(right_width * x / steps)
                    for x in range(1, steps + 1)
            ]:
                right_dob.x = x_pos
                time.sleep(0.06)

            time.sleep(8)  # leave on screen for further 8 seconds