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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddMax3PointAgendasToRestrictions < ActiveRecord::Migration[8.1]
def change
add_column :restrictions, :max_3_point_agendas, :integer
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.1].define(version: 2026_05_22_120000) do
ActiveRecord::Schema[8.1].define(version: 2026_06_11_120000) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "pgcrypto"
Expand Down Expand Up @@ -277,6 +277,7 @@
t.datetime "created_at", null: false
t.text "date_start", null: false
t.string "format_id"
t.integer "max_3_point_agendas"
t.text "name", null: false
t.integer "point_limit"
t.datetime "updated_at", null: false
Expand Down
27 changes: 27 additions & 0 deletions lib/deck_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,36 @@ def valid? # rubocop:disable Metrics/MethodLength
# Validate against Restriction
next if v.restriction_id.nil?

restriction = @restrictions[v.restriction_id]
r = @unified_restrictions[v.restriction_id]
Rails.logger.error "Restriction is #{r.inspect}"

# Check for deckbuilding restrictions (currently just the 3+ point agenda limit in startup).
if @deck['side_id'] == 'corp'
max_three_point_agendas = restriction.max_3_point_agendas
unless max_three_point_agendas.nil?
three_point_agendas = @deck['cards'].filter_map do |card_id, quantity|
card = @cards[card_id]
next unless card.card_type_id == 'agenda' && card.agenda_points.to_i >= 3

[card_id, quantity]
end
agenda_count = three_point_agendas.sum { |_card_id, quantity| quantity }

if agenda_count > max_three_point_agendas
v.add_error(
format(
'Startup Corp decks may not include more than %<limit>d agenda cards with a printed agenda point value of 3 or greater, but deck has %<count>d: %<cards>s.', # rubocop:disable Layout/LineLength
limit: max_three_point_agendas,
count: agenda_count,
cards: three_point_agendas.map { |card_id, quantity| "#{card_id} (#{quantity})" }.join(', ')
)
)
@validation_errors = true
end
end
end

# Check for banned cards.
([@deck['identity_card_id']] + @deck['cards'].keys).each do |card_id|
next unless r.key?(card_id) && r[card_id].is_banned
Expand Down
1 change: 1 addition & 0 deletions lib/tasks/cards.rake
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ namespace :cards do
name: m['name'],
date_start: m['date_start'],
point_limit: m['point_limit'],
max_3_point_agendas: m['max_3_point_agendas'],
format_id: m['format_id']
)
end
Expand Down
13 changes: 13 additions & 0 deletions spec/libraries/deck_validation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@
end
end

context 'with startup format only' do
subject(:v) { described_class.new({ 'label' => 'expand startup format', 'format_id' => 'startup' }) }

it 'expands fields' do
expect(v.label).to eq('expand startup format')
expect(v.format_id).to eq('startup')
expect(v.card_pool_id).to eq('startup_02')
expect(v.restriction_id).to eq('startup_banlist')
expect(v.snapshot_id).to eq('startup_02')
expect(v).to be_valid
end
end

context 'with card pool only' do
subject(:v) { described_class.new(card_pool_only) }

Expand Down
75 changes: 75 additions & 0 deletions spec/libraries/deck_validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,23 @@ def set_card_quantity(deck, card_id, quantity)
new_deck
end

def replace_validation(deck, validation)
new_deck = deck.deep_dup
new_deck['validations'] = [validation]
new_deck
end

def validate_for_format(deck, format_id)
replace_validation(
deck,
{
'label' => "#{format_id.capitalize} validation.",
'basic_deckbuilding_rules' => true,
'format_id' => format_id
}
)
end

def add_out_of_faction_agenda(deck)
new_deck = deck.deep_dup
new_deck['cards'].delete('send_a_message')
Expand Down Expand Up @@ -758,6 +775,64 @@ def add_out_of_faction_agenda(deck)
expect(v.errors).to include('Snapshot `snapshot_3030` does not exist.')
end

it 'applies startup restriction bans like standard restriction bans' do
deck = validate_for_format(good_asa_group, 'startup')
v = described_class.new(deck)
expect(v).not_to be_valid
expect(v.validations.size).to eq(deck['validations'].size)
expect(v.validations[0].errors).to include('Card `hedge_fund` is banned in restriction `startup_banlist`.')
end

it 'fails validation for startup corp deck with too many 3+ point agendas' do
deck = validate_for_format(good_asa_group, 'startup')
v = described_class.new(deck)
expect(v).not_to be_valid
expect(v.validations.size).to eq(deck['validations'].size)
expect(v.validations[0].errors).to include(
'Startup Corp decks may not include more than 4 agenda cards with a printed agenda point value of 3 or greater, but deck has 5: ikawah_project (3), send_a_message (2).' # rubocop:disable Layout/LineLength
)
end

it 'uses the agenda cap from the startup restriction' do
deck = replace_validation(
good_asa_group,
{
'label' => 'Startup validation.',
'basic_deckbuilding_rules' => true,
'card_pool_id' => 'startup_02',
'restriction_id' => 'startup_three_point_agenda_limit'
}
)
v = described_class.new(deck)
expect(v).not_to be_valid
expect(v.validations.size).to eq(deck['validations'].size)
expect(v.validations[0].errors).to include(
'Startup Corp decks may not include more than 3 agenda cards with a printed agenda point value of 3 or greater, but deck has 5: ikawah_project (3), send_a_message (2).' # rubocop:disable Layout/LineLength
)
end

it 'allows startup corp deck at the 3+ point agenda limit' do
deck = validate_for_format(set_card_quantity(good_asa_group, 'send_a_message', 1), 'startup')
v = described_class.new(deck)
expect(v.validations.size).to eq(deck['validations'].size)
expect(v.validations[0].errors).not_to include(a_string_matching(/Startup Corp decks may not include/))
end

it 'does not apply startup agenda cap to snapshots without a restriction' do
deck = validate_for_format(good_asa_group, 'startup')
deck['validations'][0]['snapshot_id'] = 'startup_01'
v = described_class.new(deck)
expect(v.validations.size).to eq(deck['validations'].size)
expect(v.validations[0].errors).not_to include(a_string_matching(/Startup Corp decks may not include/))
end

it 'does not apply startup agenda cap outside startup validations' do
deck = validate_for_format(good_asa_group, 'standard')
v = described_class.new(deck)
expect(v.validations.size).to eq(deck['validations'].size)
expect(v.validations[0].errors).not_to include(a_string_matching(/Startup Corp decks may not include/))
end

it 'fails validation for cards not in specified card pool' do
deck = good_asa_group.deep_dup
# Test fixture standard_02 is not a full representation of standard.
Expand Down
18 changes: 18 additions & 0 deletions test/fixtures/restrictions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ eternal_points_list:
created_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
updated_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>

startup_banlist:
id: startup_banlist
name: "Startup Banlist"
format_id: "startup"
date_start: 2026-04-17
max_3_point_agendas: 4
created_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
updated_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>

startup_three_point_agenda_limit:
id: startup_three_point_agenda_limit
name: "Startup 3 Point Agenda Limit"
format_id: "startup"
date_start: 2026-03-03
max_3_point_agendas: 3
created_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
updated_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>

standard_restricted:
id: standard_restricted
name: "Standard Restricted List"
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/restrictions_cards_banned.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ standard_banlist_trieste_model_bioroids:
created_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
updated_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>

startup_banlist_hedge_fund:
restriction_id: startup_banlist
card_id: hedge_fund
created_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
updated_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
1 change: 1 addition & 0 deletions test/fixtures/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ startup_02:
id: startup_02
format_id: startup
card_pool_id: startup_02
restriction_id: startup_banlist
date_start: 2022-09-01
active: true
created_at: <%= Time.utc(2022, 12, 8, 12, 0).to_fs(:db) %>
Expand Down
Loading