It is inappropriate of a scientist to throw Ad Hominem attacks when people remind them that all models have flaws as a direct result of their simplification of reality.
Completely inappropriate, scientist or otherwise, I would say.

Any way, here is some more of that science stuff:
The code:
def log *strings
# uncomment to output blow-by-blow
# puts *strings
end
class Game
attr_accessor :player1, :player2, :turn_count
def initialize(player1, player2)
@turn_count = 0
@player1, @player2 = player1, player2
@player1.game = self
@player2.game = self
end
def turn
@turn_count += 1
log "Turn #{@turn_count}:::"
@player1.turn(@player2)
@player2.turn(@player1) unless over?
end
def over?
@player1.hp < 1 || @player2.hp < 1 || @player1.cards.size == 0 || @player2.cards.size == 0
end
def deck_out?
@player1.cards.size == 0
end
def to_s
"#{@player1}\nvs.\n#{@player2}"
end
end
class Player
attr_accessor :hp, :quanta, :quanta_per_turn, :creatures_in_play, :card_hash,
:cards, :hand, :name, :shield, :destroy_shield_on_turn, :game
def initialize(name, hp, card_hash)
@card_hash = card_hash
@name = name
@hp = hp
@shield = 1
@quanta = @quanta_per_turn = 0
@creatures_in_play = []
@cards = @card_hash.keys.map{|ca| ca * @card_hash[ca]}.flatten
@cards = @cards.shuffle
@cards = @cards.shuffle if cards[0..6].all?{|c| !c.is_a?(Pillar)}
@cards.push(Dead.new)
@hand = []
7.times { draw }
end
def shield?
shield < 1
end
def destroy_shield!
self.shield = 1
end
def damage_per_turn
creatures_in_play.inject(0){|acc,c| acc + c.damage }
end
def turn_count
game.turn_count
end
def draw
hand << cards.shift
end
def turn(enemy)
destroy_shield! if destroy_shield_on_turn == turn_count
draw
log inspect
@hand.sort_by{|c| c.cast_order}.each{|c| c.try_cast(self, enemy) }
log "Boom!: #{enemy.hp} -= #{damage_per_turn * enemy.shield} (#{enemy.shield})"
enemy.hp -= damage_per_turn * enemy.shield
self.quanta += quanta_per_turn
log inspect, "\n"
end
def deck_size
cards.size - 1
end
def inspect
#"~#{name} hp: #{hp}" q: #{quanta} cs: #{deck_size} c_in: #{creatures_in_play.inspect}~"
"~#{name} hp: #{hp} qpt: #{quanta_per_turn} q: #{quanta} cs: #{deck_size}
dpt: #{damage_per_turn} in play: #{creatures_in_play.inspect}
in hand: #{hand.inspect}~"
end
def to_s
"#{name}(#{card_hash.inspect})"
end
end
class Card
def try_cast(me, enemy)
cast!(me, enemy) if cast?(me, enemy)
end
def cast!(me, enemy)
card_cast(me, enemy)
me.hand.slice!(me.hand.index(self))
end
def to_s
self.class.to_s
end
end
class Dead < Card
def cast?(me, enemy); true ; end
def cast!(me, enemy); end;
def cast_order; 0; end
end
class Pillar < Card
def cast?(me, enemy); true ; end
def card_cast(me, enemy)
me.quanta_per_turn += 1
end
def cast_order; 0; end
end
class Creature < Card
attr_accessor :damage, :health
def initialize(cost, damage, health)
@cost, @damage, @health = cost, damage, health
end
def cast?(me, enemy); me.quanta >= @cost; end
def card_cast(me, enemy)
me.quanta -= @cost
me.creatures_in_play << self
end
def cast_order; 100 - damage; end
def to_s
"Creature(#{damage}|#{health})"
end
end
class Bolt < Card
attr_accessor :cost, :damage
def initialize(cost, damage)
@cost, @damage = cost, damage
end
def cast?(me, enemy); me.quanta >= cost && enemy.creatures_in_play.detect{|cc| cc.health <= damage }; end
def card_cast(me, enemy)
me.quanta -= cost
target = enemy.creatures_in_play.sort_by{|cc| (cc.health > damage ? 1000 : 0) - cc.damage }.first
log "Bolt!!!!! #{target}"
enemy.creatures_in_play.slice!(enemy.creatures_in_play.index(target))
end
def cast_order; 2; end
end
class Explosion < Card
attr_accessor :cost
def initialize(cost)
@cost = cost
end
def cast?(me, enemy); me.quanta >= cost && enemy.shield != 1; end
def card_cast(me, enemy)
me.quanta -= cost
log "explosion!!!!!"
enemy.destroy_shield!
end
def cast_order; 1; end
def to_s; "Explosion(#{cost})"; end
end
class DimShield < Card
def initialize(lasting, cost)
@cost = cost
@lasting = lasting
end
def cast?(me, enemy); !me.shield? && me.quanta >= @cost && me.hp < 50; end
def card_cast(me, enemy)
me.quanta -= @cost
log "dim shield!!!!!!!!!!!!! #{me.turn_count}"
me.destroy_shield_on_turn = me.turn_count+@lasting
me.shield = 0
end
def cast_order; 1; end
def to_s
"DimShield(#{@lasting},#{@cost})"
end
end
class DuskMantel < Card
def cast?(me, enemy); !me.shield? && me.quanta >= 6; end
def card_cast(me, enemy)
me.quanta -= 6
log "dusk mantel!!!!!!!! ";
me.shield = 0.5
end
def cast_order; 1; end
end
test_decks = [
->() {Player.new('control-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 11, [Bolt.new(2,5)] => 4)},
->() {Player.new('1dusk-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 10, [DuskMantel.new] => 1, [Bolt.new(2,5)] => 4)},
->() {Player.new('3dusk-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 8, [DuskMantel.new] => 3, [Bolt.new(2,5)] => 4)},
->() {Player.new('1dim-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 10, [DimShield.new(3,6)] => 1, [Bolt.new(2,5)] => 4)},
->() {Player.new('3dim-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 8, [DimShield.new(3,6)] => 3, [Bolt.new(2,5)] => 4)},
->() {Player.new('6dim-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 5, [DimShield.new(3,6)] => 6, [Bolt.new(2,5)] => 4)},
->() {Player.new('1dim10-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 10, [DimShield.new(3,10)] => 1, [Bolt.new(2,5)] => 4)},
->() {Player.new('3dim10-4bolt', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 7, [DimShield.new(3,10)] => 3, [Bolt.new(2,5)] => 4)},
->() {Player.new('6dim10-4bolt', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 4, [DimShield.new(3,10)] => 6, [Bolt.new(2,5)] => 4)},
->() {Player.new('1dim20-4bolt', 100.0, [Pillar.new] => 15, [Creature.new(5,5,5)] => 10, [DimShield.new(3,20)] => 1, [Bolt.new(2,5)] => 4)},
->() {Player.new('3dim20-4bolt', 100.0, [Pillar.new] => 17, [Creature.new(5,5,5)] => 6, [DimShield.new(3,20)] => 3, [Bolt.new(2,5)] => 4)},
->() {Player.new('6dim20-4bolt', 100.0, [Pillar.new] => 17, [Creature.new(5,5,5)] => 3, [DimShield.new(3,20)] => 6, [Bolt.new(2,5)] => 4)},
# ->() {Player.new('control', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 14)},
# ->() {Player.new('1dusk', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 13, [DuskMantel.new] => 1)},
# ->() {Player.new('3dusk', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 11, [DuskMantel.new] => 3)},
# ->() {Player.new('1dim', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 13, [DimShield.new(3,6)] => 1)},
# ->() {Player.new('3dim', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 11, [DimShield.new(3,6)] => 3)},
# ->() {Player.new('6dim', 100.0, [Pillar.new] => 16, [Creature.new(5,5,5)] => 8, [DimShield.new(3,6)] => 6)},
]
test_decks.each do |test_deck|
damage = 0
wins = 0
losses = 0
deck_outs = 0
reps = 10000
start = ->() {
Game.new(
Player.new('rush', 10000, [Pillar.new] => 16, [Creature.new(5,5,5)] => 14),
#Player.new('blast', 10000, [Pillar.new] => 15, [Creature.new(5,5,5)] => 13, [Explosion.new(3)] => 2),
test_deck.call
)
}
reps.times do
game = start.call
game.turn until game.over?
damage += 10000 - game.player1.hp
wins += game.player1.hp <= 9900 ? 1 : 0
losses += game.player1.hp > 9900 && game.player2.hp <= 0 ? 1 : 0
deck_outs += game.player1.hp > 9900 && game.player2.hp > 0 ? 1 : 0
end
overkill = damage*1.0/reps
win_p = wins*100.0/reps
loss_p = losses*100.0/reps
deck_out_p = deck_outs*100.0/reps
puts "#{start.call.inspect} (#{reps} games)",
"overkill: #{overkill}",
"win%: #{win_p}",
"loss%:#{loss_p}",
"deckout%: #{deck_out_p}\n\n"
end
Notes:
Bolts are never used against the opponent, only against creatures.
About the results:
overkill is the average damage ignoring wins but not losses until all of player 1's cards are exhausted.
Wins, losses, and deck outs are recorded separately.
Results for Rush deck vs. shields + 4 bolts:
Spoiler for Hidden:
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
control-4bolt({[Pillar]=>15, [Creature(5|5)]=>11, [Bolt]=>4}) (10000 games)
overkill: 99.9075
win%: 49.37
loss%:50.63
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
1dusk-4bolt({[Pillar]=>15, [Creature(5|5)]=>10, [DuskMantel]=>1, [Bolt]=>4}) (10000 games)
overkill: 120.3415
win%: 66.66
loss%:33.34
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
3dusk-4bolt({[Pillar]=>15, [Creature(5|5)]=>8, [DuskMantel]=>3, [Bolt]=>4}) (10000 games)
overkill: 124.0275
win%: 74.02
loss%:25.98
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
1dim-4bolt({[Pillar]=>15, [Creature(5|5)]=>10, [DimShield(3,6)]=>1, [Bolt]=>4}) (10000 games)
overkill: 127.5435
win%: 70.21
loss%:29.79
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
3dim-4bolt({[Pillar]=>15, [Creature(5|5)]=>8, [DimShield(3,6)]=>3, [Bolt]=>4}) (10000 games)
overkill: 205.901
win%: 90.65
loss%:9.35
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
6dim-4bolt({[Pillar]=>15, [Creature(5|5)]=>5, [DimShield(3,6)]=>6, [Bolt]=>4}) (10000 games)
overkill: 303.576
win%: 97.27
loss%:2.72
deckout%: 0.01
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
1dim10-4bolt({[Pillar]=>15, [Creature(5|5)]=>10, [DimShield(3,10)]=>1, [Bolt]=>4}) (10000 games)
overkill: 117.927
win%: 61.0
loss%:39.0
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
3dim10-4bolt({[Pillar]=>16, [Creature(5|5)]=>7, [DimShield(3,10)]=>3, [Bolt]=>4}) (10000 games)
overkill: 170.6665
win%: 78.56
loss%:21.44
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
6dim10-4bolt({[Pillar]=>16, [Creature(5|5)]=>4, [DimShield(3,10)]=>6, [Bolt]=>4}) (10000 games)
overkill: 240.534
win%: 92.87
loss%:6.87
deckout%: 0.26
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
1dim20-4bolt({[Pillar]=>15, [Creature(5|5)]=>10, [DimShield(3,20)]=>1, [Bolt]=>4}) (10000 games)
overkill: 105.462
win%: 50.28
loss%:49.72
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
3dim20-4bolt({[Pillar]=>17, [Creature(5|5)]=>6, [DimShield(3,20)]=>3, [Bolt]=>4}) (10000 games)
overkill: 135.6045
win%: 62.25
loss%:37.75
deckout%: 0.0
rush({[Pillar]=>16, [Creature(5|5)]=>14})
vs.
6dim20-4bolt({[Pillar]=>17, [Creature(5|5)]=>3, [DimShield(3,20)]=>6, [Bolt]=>4}) (10000 games)
overkill: 168.567
win%: 80.49
loss%:16.85
deckout%: 2.66
TL;DR Bolts help out a lot, but dim shields are still a lot better than dusk shields across the board with or without bolts, in small numbers, or in big numbers.
With bolts, 6 Dim shields that cost 20

are better than 3 dusk shields. The results DO suggest that expensive dim shields are less "versatile" - 1 and 3 expensive
dim shields didn't fair so well. I'm not sure if that is a true signal or just a problem with the quanta balance or AI. More investigation is warranted.
I also simulated shield decks vs. a deck with 2 explosions. I noticed that tweaking the AI to start playing dim shields at 75 hp instead of 50 was highly beneficial to the win percentage against explosions, not surprisingly. The results below are with that tweak:
Results for Rush deck + 2 explosions vs. shields (no bolts this time):
Spoiler for Hidden:
blast({[Pillar]=>15, [Creature(5|5)]=>13, [Explosion(3)]=>2})
vs.
control({[Pillar]=>16, [Creature(5|5)]=>14}) (10000 games)
overkill: 92.1715
win%: 40.5
loss%:59.5
deckout%: 0.0
blast({[Pillar]=>15, [Creature(5|5)]=>13, [Explosion(3)]=>2})
vs.
1dusk({[Pillar]=>16, [Creature(5|5)]=>13, [DuskMantel]=>1}) (10000 games)
overkill: 90.43
win%: 37.59
loss%:62.41
deckout%: 0.0
blast({[Pillar]=>15, [Creature(5|5)]=>13, [Explosion(3)]=>2})
vs.
3dusk({[Pillar]=>16, [Creature(5|5)]=>11, [DuskMantel]=>3}) (10000 games)
overkill: 92.3605
win%: 42.28
loss%:57.72
deckout%: 0.0
blast({[Pillar]=>15, [Creature(5|5)]=>13, [Explosion(3)]=>2})
vs.
1dim({[Pillar]=>16, [Creature(5|5)]=>13, [DimShield(3,6)]=>1}) (10000 games)
overkill: 94.0565
win%: 41.12
loss%:58.88
deckout%: 0.0
blast({[Pillar]=>15, [Creature(5|5)]=>13, [Explosion(3)]=>2})
vs.
3dim({[Pillar]=>16, [Creature(5|5)]=>11, [DimShield(3,6)]=>3}) (10000 games)
overkill: 119.437
win%: 55.83
loss%:44.17
deckout%: 0.0
blast({[Pillar]=>15, [Creature(5|5)]=>13, [Explosion(3)]=>2})
vs.
6dim({[Pillar]=>16, [Creature(5|5)]=>8, [DimShield(3,6)]=>6}) (10000 games)
overkill: 207.0755
win%: 78.46
loss%:21.54
deckout%: 0.0
TL;DR Dim shields are still way better than dusk shields vs. explosions. I haven't run tests with expensive dim shields yet, but my intuition is that they will be worse than in other scenarios, since a 3 for 20 quanta trade is bad, mkay.
I highly encourage you to download ruby (if you haven't already) and play around with the code yourself. I would consider the results above somewhat preliminary because I didn't have time to fully optimize the decks. This is significantly more difficult with bolts and explosions in the mix, and it is harder to verify that the decisions made by the AI are acceptable. Future work, for you or me.