Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 33 additions & 49 deletions hasura/functions/match/map-veto/get_map_veto_pattern.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ CREATE OR REPLACE FUNCTION public.get_map_veto_pattern(_match public.matches) RE
AS $$
DECLARE
best_of int;
pool_size int;
pattern TEXT[] := '{}';
base_pattern TEXT[] := '{}';
i INT;
pool_size INT;
surplus INT;
-- https://docs.5stack.gg/features/map-veto
unit TEXT[] := ARRAY['Ban', 'Ban', 'Pick', 'Pick'];
unit_index int := 0;
picks_needed int;
picks_made int := 0;
steps_left int;
_type TEXT;
i INT;
BEGIN
SELECT mo.best_of INTO best_of
FROM matches m
Expand All @@ -27,57 +31,37 @@ BEGIN
RAISE EXCEPTION 'Not enough maps in the pool for the best of %', best_of USING ERRCODE = '22000';
END IF;

-- https://github.com/ValveSoftware/counter-strike_rules_and_regs/blob/main/major-supplemental-rulebook.md#map-pick-ban
picks_needed := best_of - 1;

IF best_of = 3 AND pool_size >= 4 THEN
IF pool_size = 4 THEN
base_pattern := ARRAY['Ban', 'Pick', 'Pick'];
ELSIF pool_size = 5 THEN
-- Both bans open the veto, as in the 6- and 7-map patterns below and
-- in the linked rulebook. Ban/Pick/Pick/Ban let a map be picked
-- before either team had finished banning, and it was the only BO3
-- pool that did not lead with two bans -- which is also what the
-- turn-swap in get_map_veto_picking_lineup_id assumes.
base_pattern := ARRAY['Ban', 'Ban', 'Pick', 'Pick'];
ELSIF pool_size = 6 THEN
base_pattern := ARRAY['Ban', 'Ban', 'Pick', 'Pick', 'Ban'];
-- The veto runs for pool - 1 steps; the one map nobody bans or picks is the
-- Decider, which always closes (it is auto-inserted once a single map is
-- left, so any step sitting after it could never be satisfied).
FOR i IN 1..(pool_size - 1) LOOP
-- Steps remaining, this one included.
steps_left := pool_size - i;

IF picks_made = picks_needed THEN
-- Enough maps are picked to fill the series: ban out the rest. This
-- is where a pool larger than the pattern spends its extra bans,
-- after the picks and before the Decider.
_type := 'Ban';
ELSIF steps_left = picks_needed - picks_made THEN
-- Any more banning would leave too few maps to pick from.
_type := 'Pick';
ELSE
base_pattern := ARRAY['Ban', 'Ban', 'Pick', 'Pick', 'Ban', 'Ban'];
_type := unit[unit_index % 4 + 1];
unit_index := unit_index + 1;
END IF;
ELSIF best_of = 5 AND pool_size >= 6 THEN
IF pool_size = 6 THEN
base_pattern := ARRAY['Ban', 'Pick', 'Pick', 'Pick', 'Pick'];

IF _type = 'Pick' THEN
picks_made := picks_made + 1;
-- Every Pick is answered by a Side.
pattern := pattern || ARRAY['Pick', 'Side'];
ELSE
base_pattern := ARRAY['Ban', 'Ban', 'Pick', 'Pick', 'Pick', 'Pick'];
pattern := pattern || ARRAY['Ban'];
END IF;
ELSE
-- Everything the rulebook doesn't cover (BO1, a pool only big enough to
-- pick from, any other best of): pick the maps that get played and let
-- the bans below account for the rest. Without this arm an unsupported
-- best of returned a pattern of NULLs and the veto could never finish.
base_pattern := array_fill('Pick'::text, ARRAY[best_of - 1]);
END IF;

-- Maps the pattern doesn't account for are banned up front, trimming the
-- pool to the rulebook shape before the picks. The Decider closes: it is
-- only ever auto-inserted by create_match_map_from_veto once one map is
-- left, so any step sitting after it can never be satisfied.
surplus := pool_size - 1 - coalesce(array_length(base_pattern, 1), 0);
base_pattern := array_append(
array_fill('Ban'::text, ARRAY[surplus]) || base_pattern,
'Decider'
);

FOR i IN 1..(pool_size) LOOP
_type := base_pattern[i];

pattern := pattern ||
CASE
WHEN _type = 'Pick' THEN ARRAY['Pick', 'Side']
ELSE ARRAY[_type]
END;
END LOOP;

RETURN pattern;
RETURN pattern || ARRAY['Decider'];
END;
$$;
24 changes: 11 additions & 13 deletions hasura/functions/match/map-veto/get_map_veto_picking_lineup_id.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ DECLARE
action_index int;
next_action text;
turn_index int;
best_of int;
picks_made int;
current_team int;
team int;
last_pick_lineup uuid;
BEGIN
IF match.status != 'Veto' THEN
Expand Down Expand Up @@ -50,21 +49,20 @@ BEGIN
WHERE match_id = match.id
AND type IN ('Ban', 'Pick', 'Decider');

select mo.best_of into best_of from matches m
inner join match_options mo on mo.id = m.match_options_id
where m.id = match.id;
SELECT COUNT(*) INTO picks_made
FROM match_map_veto_picks
WHERE match_id = match.id
AND type = 'Pick';

-- best of 3 swaps teams after the 4th pick
IF best_of = 3 THEN
IF turn_index < 4 THEN
current_team := CASE WHEN turn_index % 2 = 0 THEN 1 ELSE 2 END;
ELSE
current_team := CASE WHEN turn_index % 2 = 0 THEN 2 ELSE 1 END;
END IF;
-- Turns alternate, and every completed pair of picks reverses who leads, so
-- the picks snake: lineup 1, lineup 2, lineup 2, lineup 1. Without the
-- reverse the team that opens the veto takes every odd pick and the last
-- ban before the decider.
IF (picks_made / 2) % 2 = 1 THEN
current_team := CASE WHEN turn_index % 2 = 0 THEN 2 ELSE 1 END;
ELSE
current_team := CASE WHEN turn_index % 2 = 0 THEN 1 ELSE 2 END;
END IF;


IF current_team = 1 THEN
RETURN match.lineup_1_id;
Expand Down
31 changes: 29 additions & 2 deletions hasura/functions/match/map-veto/verify_map_veto_pick.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CREATE OR REPLACE FUNCTION public.verify_map_veto_pick(match_map_veto_pick match
DECLARE
pickType VARCHAR(255);
lineup_id uuid;
picked_map_id uuid;
_match matches;
map_pool uuid[];
use_active_pool BOOLEAN;
Expand Down Expand Up @@ -37,8 +38,34 @@ BEGIN
END IF;

-- Ensure that a side is picked for 'Side' type veto
IF pickType = 'Side' AND match_map_veto_pick.side IS NULL THEN
RAISE EXCEPTION 'Must pick a side' USING ERRCODE = '22000';
IF pickType = 'Side' THEN
IF match_map_veto_pick.side IS NULL THEN
RAISE EXCEPTION 'Must pick a side' USING ERRCODE = '22000';
END IF;

-- A Side answers the Pick before it and nothing else. Unchecked, the
-- side could be recorded against the leftover map -- the decider, which
-- no one ever picks sides on -- leaving the picked map unplayed and the
-- veto stuck on a Decider step with no maps left to satisfy it.
-- Picks and sides alternate, so only one picked map is ever waiting on
-- a side. Matched on the rows themselves rather than the latest
-- created_at, which ties when picks share a transaction.
SELECT mvp.map_id INTO picked_map_id
FROM match_map_veto_picks mvp
WHERE mvp.match_id = match_map_veto_pick.match_id
AND mvp.type = 'Pick'
AND NOT EXISTS (
SELECT 1
FROM match_map_veto_picks sided
WHERE sided.match_id = mvp.match_id
AND sided.map_id = mvp.map_id
AND sided.type = 'Side'
)
LIMIT 1;

IF match_map_veto_pick.map_id IS DISTINCT FROM picked_map_id THEN
RAISE EXCEPTION 'Must pick a side for the map that was just picked' USING ERRCODE = '22000';
END IF;
END IF;

-- Ensure that a side is not picked for 'Pick' or 'Ban' type veto
Expand Down
Loading
Loading