🎵 Song Quiz Blueprint – A Music Trivia Game for Home Assistant Voice!

Hey everyone!

I recently transitioned from Amazon Echo devices to Home Assistant Voice, and one of the things I missed the most was playing Song Quiz. So, I decided to recreate it in Home Assistant using Music Assistant and conversation voice commands! :microphone::notes:

This blueprint allows you to play a music trivia game with up to four players. The game plays songs from a selected era, and players have to guess the song and artist to earn points. After five rounds, the player with the highest score wins!

For now you need to use your wake word, after every questions the voice satellite asks. I want to change that in the future when assist_satellite.start_conversation becomes available.

You can change the Spotify playlist that it will use for each era or just go with my defaults.

:hammer_and_wrench: Setup Instructions

Requirements:

  • Music Assistant integration installed and configured
  • A working Home Assistant Voice setup
  • Four counters to track player scores:
    • counter.song_quiz_player_1
    • counter.song_quiz_player_2
    • counter.song_quiz_player_3
    • counter.song_quiz_player_4

How to Play

  1. Say “Start Song Quiz” to begin.
  2. Home Assistant will ask how many players are playing (1 player, 2 players,…)
  3. Choose a music era: 90s, 2000s, 2010s, or Now.
  4. Each player listens to a song snippet and tries to guess the song and artist.
  5. Players earn 25 points for guessing both correctly, 10 points for getting one right.
  6. After 5 rounds, the game announces the winner!

Voice Commands:

  • :green_circle: “Start Song Quiz” – Begins a new game
  • :red_circle: “Stop Song Quiz” – Ends the game

:link: Download the Blueprint

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

Paste the YAML into Automations > Blueprints, or grab it from my GitHub:
:point_right: GitHub Link

I’d love to hear feedback from the community! Let me know if you try it out or if you have any feature suggestions. :rocket::notes:

blueprint:
  name: Song Quiz
  description: >-
    A voice-controlled music trivia game using Home Assistant Voice and Music Assistant. Players guess
    songs based on a selected era, earning points for correct answers. The game
    lasts 5 rounds, and the player with the highest score wins.
    
    **Setup Required:** Ensure you have the following counters set up in Home Assistant:
    - `counter.song_quiz_player_1`
    - `counter.song_quiz_player_2`
    - `counter.song_quiz_player_3`
    - `counter.song_quiz_player_4`
  domain: automation
  input:
    game_rounds:
      name: Game Rounds
      description: How many rounds a game has. 
      default: 5
    default_volume:
      name: Default Volume
      description: Default volume for playing the music.
      default: 0.6
    spotify_uris_90s:
      name: Spotify URI for 90s
      description: Playlist URI for the 90s era.
      default: "spotify:playlist:37i9dQZF1DXbTxeAdrVG2l"
    spotify_uris_2000s:
      name: Spotify URI for 2000s
      description: Playlist URI for the 2000s era.
      default: "spotify:playlist:37i9dQZF1DX4o1oenSJRJd"
    spotify_uris_2010s:
      name: Spotify URI for 2010s
      description: Playlist URI for the 2010s era.
      default: "spotify:playlist:37i9dQZF1DX4UtSsGT1Sbe"
    spotify_uris_now:
      name: Spotify URI for Now
      description: Playlist URI for current hits.
      default: "spotify:playlist:37i9dQZEVXbMDoHDwVN2tF"
    spotify_uris_default:
      name: Spotify URI for fallback
      description: Playlist URI as a fallback if no era is set.
      default: "spotify:playlist:37i9dQZF1DX4UtSsGT1Sbe"

trigger:
  - command:
      - song quiz
      - start song quiz
    trigger: conversation
  - trigger: conversation
    command: stop song quiz
    id: stop

actions:
  - variables:
      assist_area: "{{ trigger.device_id | device_attr('area_id') }}"
      media_player_temp: >-
        {% set players = area_entities(assist_area) | select('match', 'media_player\\..*') | list %}
        {% set valid_players = players | select('in', integration_entities('music_assistant')) | list %}
        {{ valid_players | first if valid_players else none }}
      media_player: "{{ media_player_temp if media_player_temp else 'media_player.' ~ trigger.device_id.split('.')[1] }}"
  - choose:
      - conditions:
          - condition: trigger
            id:
              - stop
        sequence:
          - action: media_player.media_pause
            metadata: {}
            data: {}
            target:
              entity_id: "{{ media_player }}"
          - set_conversation_response: Song quiz stopped. Thanks for playing!
    default:
      - if:
          - condition: template
            value_template: "{{ is_state(media_player,'playing') }}"
        then:
          - action: media_player.media_pause
            data: {}
            target:
              entity_id: "{{ media_player }}"
      - action: counter.reset
        target:
          entity_id:
            - counter.song_quiz_player_1
            - counter.song_quiz_player_2
            - counter.song_quiz_player_3
            - counter.song_quiz_player_4
        data: {}
      - target:
          device_id: "{{ trigger.device_id }}"
        data:
          message: >-
            Welcome to Song Quiz! How many players are playing? Names are
            optional.
        action: assist_satellite.announce
      - wait_for_trigger:
          - trigger: conversation
            command:
              - "{number} players"
              - "{number} player"
              - "{number} players {names}"
        continue_on_timeout: false
        timeout:
          hours: 0
          minutes: 0
          seconds: 30
          milliseconds: 0
      - variables:
          num_players: >-
            {% set mapping = {'One': 1, 'Two': 2, 'Three': 3, 'Four': 4} %} {{
            mapping.get(wait.trigger.slots.number, 1) }}
          names_specified: "{{ 'names' in wait.trigger.slots }}"
          player_names: |-
            {% if names_specified %}
              {{ wait.trigger.slots.names.replace(' and ', ' ').split(' ')[:num_players] }}
            {% else %}
              {{ ['Player 1', 'Player 2', 'Player 3', 'Player 4'][:num_players] }}
            {% endif %}
      - target:
          device_id: "{{ trigger.device_id }}"
        data:
          message: >-
            {% if names_specified %}Welcome, {{ player_names | join(', ') }}!{%
            else %}Great, {{ num_players }} Players! {% endif %} Choose a music
            era: 90s, 2000s, 2010s or now.
        action: assist_satellite.announce
      - wait_for_trigger:
          - trigger: conversation
            command:
              - "{time}"
        continue_on_timeout: false
        timeout:
          hours: 0
          minutes: 0
          seconds: 30
          milliseconds: 0
      - variables:
          era: "{{ wait.trigger.slots.time }}"
          playlist_uri: >-
            {% if era == '90s' %} !input spotify_uris_90s
            {% elif era == '2000s' %} !input spotify_uris_2000s
            {% elif era == '2010s' %} !input spotify_uris_2010s
            {% elif era == 'now' %} !input spotify_uris_now
            {% else %} !input spotify_uris_default {% endif %}

      - variables:
          round: 0
      - repeat:
          count: !input game_rounds
          sequence:
            - variables:
                round: "{{ repeat.index }}"
            - target:
                device_id: "{{ trigger.device_id }}"
              data:
                message: Round {{ round }} begins!
              action: assist_satellite.announce
            - repeat:
                count: "{{ num_players }}"
                sequence:
                  - variables:
                      player: "{{ repeat.index }}"
                      default_volume: !input default_volume
                      current_volume: >-
                        {{ current_volume if current_volume else default_volume
                        }}
                  - if:
                      - condition: template
                        value_template: "{{ not is_state(media_player,\"playing\") }}"
                    then:
                      - action: music_assistant.play_media
                        data:
                          media_type: playlist
                          media_id: "{{ playlist_uri | string }}"
                        target:
                          entity_id: "{{ media_player }}"
                      - delay:
                          hours: 0
                          minutes: 0
                          seconds: 5
                          milliseconds: 0
                      - action: media_player.shuffle_set
                        data:
                          shuffle: true
                        target:
                          entity_id: "{{ media_player }}"
                      - delay:
                          hours: 0
                          minutes: 0
                          seconds: 2
                          milliseconds: 0
                      - variables:
                          current_volume: "{{ state_attr(media_player, 'volume_level') }}"
                      - data:
                          entity_id: "{{ media_player }}"
                          volume_level: 0.2
                        action: media_player.volume_set
                  - target:
                      device_id: "{{ trigger.device_id }}"
                    data:
                      message: "{{ player_names[player - 1] }}, listen to your song!"
                    action: assist_satellite.announce
                  - action: media_player.media_next_track
                    data: {}
                    target:
                      entity_id: "{{ media_player }}"
                  - data:
                      entity_id: "{{ media_player }}"
                      volume_level: "{{ current_volume | float }}"
                    action: media_player.volume_set
                  - delay: "00:00:30"
                  - variables:
                      current_volume: "{{ state_attr(media_player, 'volume_level') }}"
                  - data:
                      entity_id: "{{ media_player }}"
                      volume_level: 0.2
                    action: media_player.volume_set
                  - target:
                      device_id: "{{ trigger.device_id }}"
                    data:
                      message: "{{ player_names[player - 1] }}, what is your guess?"
                    action: assist_satellite.announce
                  - wait_for_trigger:
                      - trigger: conversation
                        command:
                          - "{guess}"
                    continue_on_timeout: true
                    timeout:
                      hours: 0
                      minutes: 0
                      seconds: 30
                      milliseconds: 0
                  - variables:
                      answer: "{{ wait.trigger.slots.guess | lower }}"
                      correct_song: >-
                        {% set song = state_attr(media_player, 'media_title') |
                        lower %} {% if song is string %}
                          {% set song = song | regex_replace(find='\[.*?\]|\(.*?\)', replace='') %} {# Remove anything in brackets #}
                          {% set song = song | regex_replace(find='[0-9!?.,]', replace='') %} {# Remove numbers, exclamation marks, and punctuation #}
                          {{ song | trim }}
                        {% else %}
                          ""
                        {% endif %}
                      correct_artist: >-
                        {% set artist = state_attr(media_player, 'media_artist')
                        | lower %} {% if artist is string %}
                          {% set artist = artist.split('/')[0] %}  {# Take only the first part if there's a '/' #}
                          {% set artist = artist | regex_replace(find='[0-9&.-]', replace='', ignorecase=True) %}  {# Remove numbers and '&' #}
                          {{ artist | trim }}
                        {% else %}
                          {% if artist is iterable %}
                            {{ artist | join(' / ') | regex_replace(find='[0-9&.-]', replace='', ignorecase=True) | trim }}
                          {% else %}
                            ""
                          {% endif %}
                        {% endif %}
                      counter_entity: |-
                        {% if player == 1 %}
                          counter.song_quiz_player_1
                        {% elif player == 2 %}
                          counter.song_quiz_player_2
                        {% elif player == 3 %}
                          counter.song_quiz_player_3
                        {% else %}
                          counter.song_quiz_player_4
                        {% endif %}
                  - choose:
                      - conditions:
                          - condition: template
                            value_template: >-
                              {{ correct_song in answer | lower and
                              correct_artist in answer | lower }}
                        sequence:
                          - action: counter.set_value
                            data:
                              value: >-
                                {{ states(counter_entity) |
                                int + 25 }}
                            target:
                              entity_id: "{{ counter_entity }}"
                          - target:
                              device_id: "{{ trigger.device_id }}"
                            data:
                              message: >-
                                Bazinga! {{ player_names[player - 1] }} now has {{
                                states(counter_entity) }}
                                points.
                            action: assist_satellite.announce
                      - conditions:
                          - condition: template
                            value_template: >-
                              {{ correct_song in answer | lower or
                              correct_artist in answer | lower }}
                        sequence:
                          - action: counter.set_value
                            data:
                              value: >-
                                {{ states(counter_entity) |
                                int + 10 }}
                            target:
                              entity_id: "{{ counter_entity }}"
                          - target:
                              device_id: "{{ trigger.device_id }}"
                            data:
                              message: >-
                                Great Job! {{ player_names[player - 1] }} now has {{
                                states(counter_entity) }}
                                points.
                            action: assist_satellite.announce
                    default:
                      - target:
                          device_id: "{{ trigger.device_id }}"
                        data:
                          message: >-
                            Sorry thats incorrect! {{ player_names[player - 1] }} now
                            has {{ states(counter_entity) }}
                            points.
                        action: assist_satellite.announce
            - target:
                device_id: "{{ trigger.device_id }}"
              data:
                message: >-
                  End of round {{ round }}! Here are the standings: {{
                  player_names[0] }}: {{ states("counter.song_quiz_player_1") }}
                  points, {% if num_players > 1 %} {{ player_names[1] }}: {{
                  states("counter.song_quiz_player_2") }} points, {% endif %} {%
                  if num_players > 2 %} {{ player_names[2] }}: {{
                  states("counter.song_quiz_player_3") }} points, {% endif %} {%
                  if num_players > 3 %} {{ player_names[4] }}: {{
                  states("counter.song_quiz_player_4") }} points. {% endif %}
              action: assist_satellite.announce
      - target:
          device_id: "{{ trigger.device_id }}"
        data:
          message: "Game over! Let's see the final scores:"
        action: assist_satellite.announce
      - repeat:
          count: "{{ num_players }}"
          sequence:
            - variables:
                player: "{{ repeat.index }}"
            - target:
                device_id: "{{ trigger.device_id }}"
              data:
                message: >-
                  {{ player_names[player - 1] }} scored {{
                  states("counter.song_quiz_player_1") if player == 1 else
                  states("counter.song_quiz_player_2") if player == 2 else
                  states("counter.song_quiz_player_3") if player == 3 else
                  states("counter.song_quiz_player_4") }} points.
              action: assist_satellite.announce
      - variables:
          scores: |-
            {% set score_list = [
              states("counter.song_quiz_player_1") | int,
              states("counter.song_quiz_player_2") | int,
              states("counter.song_quiz_player_3") | int,
              states("counter.song_quiz_player_4") | int
            ] %} {{ score_list[:num_players] }}
          highest_score: "{{ scores | max }}"
          winners: |-
            {% if highest_score %}
              {% set player_ids = [1, 2, 3, 4][:num_players] %}
              {% set ns = namespace(winners=[]) %}
              {% for i in range(num_players) %}
                {% if scores[i] == highest_score %}
                  {% set ns.winners = ns.winners + [player_ids[i]] %}
                {% endif %}
              {% endfor %}
              {{ ns.winners }}
            {% else %}
              []
            {% endif %}
      - target:
          device_id: "{{ trigger.device_id }}"
        data:
          message: |-
            {% if not highest_score %}
              No winner this time! Better luck next time!
            {% elif winners | length == 1 %}
              The winner is Player {{ winners[0] }}! Congratulations!
            {% else %}
              It's a tie between Players {{ winners | join(' and ') }}! Well played!
            {% endif %}
        action: assist_satellite.announce
      - action: media_player.media_pause
        metadata: {}
        data: {}
        target:
          entity_id: "{{ media_player }}"
mode: restart
5 Likes

I did a few edits. I added an input for the default music volume and the number of game rounds, so it is a bit more customizable.

I am also working on an LLM-enhanced version. It uses an LLM to determine if the answers is correct, which gives way better results, and generates a custom end message to announce the winner. If there is interest for it I can share it as well.

I did another upgrade, it is now possible to set names for the players. This is optional though.

This looks fun!

1 Like