| @@ -0,0 +1,142 @@ | |||
| 1 | + | 1. Helpers (configuration.yaml, or create them in the UI) | |
| 2 | + | ||
| 3 | + | input_button: | |
| 4 | + | station_game_start: | |
| 5 | + | name: Start station game | |
| 6 | + | icon: mdi:bus-clock | |
| 7 | + | ||
| 8 | + | # One dropdown per player. Options = "-" (no pick) + station names. | |
| 9 | + | # Station names must match the keys in the automation's `stations` map. | |
| 10 | + | input_select: | |
| 11 | + | station_game_alice: | |
| 12 | + | name: Alice's station | |
| 13 | + | icon: mdi:account | |
| 14 | + | options: ["-", "Stratford", "Hackney Wick", "Canning Town", "Bow Road"] | |
| 15 | + | station_game_bob: | |
| 16 | + | name: Bob's station | |
| 17 | + | icon: mdi:account | |
| 18 | + | options: ["-", "Stratford", "Hackney Wick", "Canning Town", "Bow Road"] | |
| 19 | + | station_game_carol: | |
| 20 | + | name: Carol's station | |
| 21 | + | icon: mdi:account | |
| 22 | + | options: ["-", "Stratford", "Hackney Wick", "Canning Town", "Bow Road"] | |
| 23 | + | ||
| 24 | + | 2. Automation | |
| 25 | + | ||
| 26 | + | alias: Station arrival game | |
| 27 | + | description: Everyone picks a station; soonest next arrival wins. | |
| 28 | + | mode: single | |
| 29 | + | triggers: | |
| 30 | + | - trigger: state | |
| 31 | + | entity_id: input_button.station_game_start | |
| 32 | + | not_from: [unavailable, unknown] | |
| 33 | + | variables: | |
| 34 | + | pick_timeout: 60 # seconds players get to choose | |
| 35 | + | departures_attr: departures # attribute holding the arrivals list | |
| 36 | + | notify_action: notify.notify | |
| 37 | + | stations: | |
| 38 | + | Stratford: sensor.stratford_departures | |
| 39 | + | Hackney Wick: sensor.hackney_wick_departures | |
| 40 | + | Canning Town: sensor.canning_town_departures | |
| 41 | + | Bow Road: sensor.bow_road_departures | |
| 42 | + | players: | |
| 43 | + | Alice: input_select.station_game_alice | |
| 44 | + | Bob: input_select.station_game_bob | |
| 45 | + | Carol: input_select.station_game_carol | |
| 46 | + | actions: | |
| 47 | + | # Reset everyone's pick | |
| 48 | + | - action: input_select.select_option | |
| 49 | + | target: | |
| 50 | + | entity_id: "{{ players.values() | list }}" | |
| 51 | + | data: | |
| 52 | + | option: "-" | |
| 53 | + | ||
| 54 | + | - action: "{{ notify_action }}" | |
| 55 | + | data: | |
| 56 | + | title: "π Station game!" | |
| 57 | + | message: > | |
| 58 | + | Pick your station within {{ pick_timeout }}s. | |
| 59 | + | Soonest arrival wins. Options: {{ stations.keys() | join(', ') }} | |
| 60 | + | ||
| 61 | + | # Wait until everyone has picked, or time runs out | |
| 62 | + | - wait_template: > | |
| 63 | + | {{ players.values() | map('states') | select('eq', '-') | list | count == 0 }} | |
| 64 | + | timeout: | |
| 65 | + | seconds: "{{ pick_timeout }}" | |
| 66 | + | continue_on_timeout: true | |
| 67 | + | ||
| 68 | + | # Find each player's soonest future arrival | |
| 69 | + | - variables: | |
| 70 | + | results: > | |
| 71 | + | {% set ns = namespace(r=[]) %} | |
| 72 | + | {% for player, sel in players.items() %} | |
| 73 | + | {% set station = states(sel) %} | |
| 74 | + | {% set sensor = stations.get(station) %} | |
| 75 | + | {% if sensor %} | |
| 76 | + | {% set best = namespace(t=none, d=none) %} | |
| 77 | + | {% for d in state_attr(sensor, departures_attr) or [] %} | |
| 78 | + | {% set t = as_datetime(d.expected) if d.expected else none %} | |
| 79 | + | {% if t and t > now() and (best.t is none or t < best.t) %} | |
| 80 | + | {% set best.t = t %} | |
| 81 | + | {% set best.d = d %} | |
| 82 | + | {% endif %} | |
| 83 | + | {% endfor %} | |
| 84 | + | {% if best.t is not none %} | |
| 85 | + | {% set ns.r = ns.r + [{ | |
| 86 | + | 'player': player, | |
| 87 | + | 'station': station, | |
| 88 | + | 'secs': (best.t - now()).total_seconds() | int, | |
| 89 | + | 'line': best.d.line.designation if best.d.line is mapping else '?', | |
| 90 | + | 'mode': best.d.line.transport_mode if best.d.line is mapping else '', | |
| 91 | + | 'dest': best.d.destination | |
| 92 | + | }] %} | |
| 93 | + | {% endif %} | |
| 94 | + | {% endif %} | |
| 95 | + | {% endfor %} | |
| 96 | + | {{ ns.r | sort(attribute='secs') }} | |
| 97 | + | no_pick: > | |
| 98 | + | {{ players.items() | selectattr('1', 'is_state', '-') | map(attribute='0') | list }} | |
| 99 | + | winners: > | |
| 100 | + | {% if results | count > 0 %} | |
| 101 | + | {{ results | selectattr('secs', 'eq', results[0].secs) | map(attribute='player') | list }} | |
| 102 | + | {% else %}[]{% endif %} | |
| 103 | + | ||
| 104 | + | - choose: | |
| 105 | + | - conditions: "{{ winners | count > 0 }}" | |
| 106 | + | sequence: | |
| 107 | + | - action: "{{ notify_action }}" | |
| 108 | + | data: | |
| 109 | + | title: > | |
| 110 | + | π {{ winners | join(' & ') }} win{{ 's' if winners | count == 1 }}! | |
| 111 | + | message: > | |
| 112 | + | {% for r in results -%} | |
| 113 | + | {{ loop.index }}. {{ r.player }} β {{ r.station }}: | |
| 114 | + | {{ r.mode | lower }} {{ r.line }} β {{ r.dest }} | |
| 115 | + | in {{ r.secs // 60 }}m {{ '%02d' % (r.secs % 60) }}s | |
| 116 | + | {% endfor -%} | |
| 117 | + | {% if no_pick %}Didn't pick: {{ no_pick | join(', ') }}{% endif %} | |
| 118 | + | default: | |
| 119 | + | - action: "{{ notify_action }}" | |
| 120 | + | data: | |
| 121 | + | title: "π Station game" | |
| 122 | + | message: "No valid picks or no upcoming arrivals. Nobody wins." | |
| 123 | + | ||
| 124 | + | 3. Dashboard card (optional) | |
| 125 | + | ||
| 126 | + | type: entities | |
| 127 | + | title: Station game | |
| 128 | + | entities: | |
| 129 | + | - input_button.station_game_start | |
| 130 | + | - input_select.station_game_alice | |
| 131 | + | - input_select.station_game_bob | |
| 132 | + | - input_select.station_game_carol | |
| 133 | + | ||
| 134 | + | How it works | |
| 135 | + | ||
| 136 | + | - Start: press the button. Everyone's pick resets to - and a notification goes out. | |
| 137 | + | - Picking: each player sets their own dropdown. The game ends early once all players have picked, otherwise after 60s. Anyone who didn't | |
| 138 | + | pick is left out and named in the results. | |
| 139 | + | - Winner: for each pick, the automation takes the earliest expected time that is still in the future (it skips past arrivals). The lowest | |
| 140 | + | wait wins. | |
| 141 | + | - Ties: if two players pick the same station or get identical times, they share the win. | |
| 142 | + | - Results: the notification shows the full ranking with mode, line, destination and countdown. | |
mihail revised this gist 3 hours ago Β· 04c3476
1 file changed, 142 insertions