Skip to content

Commit ef5a25f

Browse files
committed
London Mulligan
1 parent 7d3aeb1 commit ef5a25f

File tree

7 files changed

+166
-75
lines changed

7 files changed

+166
-75
lines changed

src/Game.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import OpenCards from './Cards.js';
44
import enums from './enum.json' with { type: 'json' };
55
import * as wasm from './rs/pkg/etg.js';
66

7-
const GameMoveType = ['end', 'cast', 'accept', 'mulligan', 'foe', 'resign'];
7+
const GameMoveType = [
8+
'end',
9+
'cast',
10+
'accept',
11+
'mulligan',
12+
'shuffle',
13+
'foe',
14+
'resign',
15+
];
816

917
export default class Game {
1018
constructor(data) {

src/rs/src/aieval.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,11 +1017,7 @@ fn evalthing(
10171017
.filter(|&r| r != 0)
10181018
.map(|r| eval_skill(ctx, r, ctx.getSkill(r, Event::Death), ttatk, damage, quantamap))
10191019
.sum::<i32>();
1020-
if j == 0 {
1021-
score += val
1022-
} else {
1023-
score -= val
1024-
}
1020+
if j == 0 { score += val } else { score -= val }
10251021
}
10261022
}
10271023
}
@@ -1095,7 +1091,16 @@ fn evalthing(
10951091
} else {
10961092
score = if ctx.material(id, None) { score * 5 / 4 } else { score * 7 / 2 };
10971093
}
1098-
score * if inhand { 2 } else if ctx.hasskill(id, Event::Turnstart, Skill::beguilestop) && !ctx.hasskill(id, Event::OwnAttack, Skill::singularity) { -1 } else { 3 }
1094+
score
1095+
* if inhand {
1096+
2
1097+
} else if ctx.hasskill(id, Event::Turnstart, Skill::beguilestop)
1098+
&& !ctx.hasskill(id, Event::OwnAttack, Skill::singularity)
1099+
{
1100+
-1
1101+
} else {
1102+
3
1103+
}
10991104
}
11001105

11011106
pub fn eval(ctx: &Game) -> i32 {

src/rs/src/aisearch.rs

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,21 @@ struct Candidate {
3030
pub score: i32,
3131
}
3232

33-
fn get_worst_card(ctx: &Game) -> (i16, i32) {
34-
if ctx.full_hand(ctx.turn) {
35-
ctx.get_player(ctx.turn)
36-
.hand
37-
.into_iter()
38-
.filter(|&card| card != 0)
39-
.map(|card| {
40-
let mut clone = ctx.clonegame();
41-
clone.die(card);
42-
(card, eval(&clone))
43-
})
44-
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal))
45-
.unwrap()
46-
} else {
47-
(0, eval(ctx))
48-
}
33+
fn get_worst_card(ctx: &Game) -> Option<(i16, i32)> {
34+
ctx.get_player(ctx.turn)
35+
.hand
36+
.into_iter()
37+
.filter(|&card| card != 0)
38+
.map(|card| {
39+
let mut clone = ctx.clonegame();
40+
clone.die(card);
41+
(card, eval(&clone))
42+
})
43+
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal))
44+
}
45+
46+
fn get_discard_card(ctx: &Game) -> (i16, i32) {
47+
if ctx.full_hand(ctx.turn) { get_worst_card(ctx).unwrap() } else { (0, eval(ctx)) }
4948
}
5049

5150
fn lethal(ctx: &Game) -> Option<GameMove> {
@@ -235,7 +234,7 @@ fn scancore(ctx: &Game, depth: i32, candy: &mut Candidate, limit: &mut u32, cmd:
235234
}) {
236235
gclone.r#move(cmd);
237236
}
238-
let score = get_worst_card(&gclone).1;
237+
let (_, score) = get_discard_card(&gclone);
239238
if score > candy.score || (score == candy.score && depth < candy.depth) {
240239
if depth == 0 {
241240
*candy = Candidate { cmd, depth, score };
@@ -286,28 +285,33 @@ fn scan(ctx: &Game, depth: i32, candy: &mut Candidate, limit: &mut u32) {
286285

287286
pub fn search(ctx: &Game) -> GameMove {
288287
if ctx.phase == Phase::Mulligan {
289-
let turn = ctx.turn;
290-
let pl = ctx.get_player(turn);
291-
return if pl.hand_len() < 6
292-
|| pl.hand_iter().any(|id| {
293-
ctx.get(id, Flag::pillar) || {
294-
let card = ctx.get(id, Stat::card);
295-
card::IsOf(card, card::Nova)
296-
|| card::IsOf(card, card::Immolation)
297-
|| card::IsOf(card, card::GiftofOceanus)
298-
|| card::IsOf(card, card::QuantumLocket)
299-
}
300-
}) || pl.deck.iter().all(|&id| !ctx.get(id, Flag::pillar))
288+
let pl = ctx.get_player(ctx.turn);
289+
if ctx.tax_left(ctx.turn) != 0 {
290+
if let Some((discard, _)) = get_worst_card(ctx) {
291+
return GameMove::Shuffle(discard);
292+
} else {
293+
return GameMove::Accept;
294+
}
295+
} else if pl.hand_iter().any(|id| {
296+
ctx.get(id, Flag::pillar) || {
297+
let card = ctx.get(id, Stat::card);
298+
card::IsOf(card, card::Nova)
299+
|| card::IsOf(card, card::Immolation)
300+
|| card::IsOf(card, card::GiftofOceanus)
301+
|| card::IsOf(card, card::QuantumLocket)
302+
}
303+
}) || pl.deck.iter().all(|&id| !ctx.get(id, Flag::pillar))
301304
{
302305
GameMove::Accept
303306
} else {
304307
GameMove::Mulligan
305-
};
308+
}
309+
} else {
310+
lethal(ctx).unwrap_or_else(|| {
311+
let (discard, score) = get_discard_card(ctx);
312+
let mut candy = Candidate { cmd: GameMove::End(discard), depth: 0, score };
313+
scan(ctx, 0, &mut candy, &mut 5040);
314+
candy.cmd
315+
})
306316
}
307-
lethal(ctx).unwrap_or_else(|| {
308-
let (discard, score) = get_worst_card(ctx);
309-
let mut candy = Candidate { cmd: GameMove::End(discard), depth: 0, score };
310-
scan(ctx, 0, &mut candy, &mut 5040);
311-
candy.cmd
312-
})
313317
}

src/rs/src/game.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,10 @@ pub struct PlayerData {
8383
pub markpower: i8,
8484
pub deckpower: u8,
8585
pub drawpower: u8,
86+
pub tax: u8,
87+
pub quanta: [u8; 12],
8688
pub creatures: [i16; 23],
8789
pub permanents: [i16; 16],
88-
pub quanta: [u8; 12],
8990
pub hand: [i16; 8],
9091
pub deck: Rc<Vec<i16>>,
9192
}
@@ -157,6 +158,7 @@ pub enum GameMove {
157158
Cast(i16, i16),
158159
Accept,
159160
Mulligan,
161+
Shuffle(i16),
160162
Foe(i16),
161163
Resign(i16),
162164
}
@@ -168,8 +170,9 @@ impl From<GameMove> for [i16; 3] {
168170
GameMove::Cast(c, t) => [1, c, t],
169171
GameMove::Accept => [2, 0, 0],
170172
GameMove::Mulligan => [3, 0, 0],
171-
GameMove::Foe(t) => [4, 0, t],
172-
GameMove::Resign(c) => [5, c, 0],
173+
GameMove::Shuffle(t) => [4, 0, t],
174+
GameMove::Foe(t) => [5, 0, t],
175+
GameMove::Resign(c) => [6, c, 0],
173176
}
174177
}
175178
}
@@ -181,8 +184,9 @@ impl From<[i16; 3]> for GameMove {
181184
1 => GameMove::Cast(cmd[1], cmd[2]),
182185
2 => GameMove::Accept,
183186
3 => GameMove::Mulligan,
184-
4 => GameMove::Foe(cmd[2]),
185-
5 => GameMove::Resign(cmd[1]),
187+
4 => GameMove::Shuffle(cmd[2]),
188+
5 => GameMove::Foe(cmd[2]),
189+
6 => GameMove::Resign(cmd[1]),
186190
_ => GameMove::Mulligan,
187191
}
188192
}
@@ -679,6 +683,15 @@ impl Game {
679683
self.get_player(id).quanta(ele)
680684
}
681685

686+
pub fn tax_left(&self, id: i16) -> u8 {
687+
let player = self.get_player(id);
688+
player.tax - (7 - player.hand_len() as u8)
689+
}
690+
691+
pub fn tax(&self, id: i16) -> u8 {
692+
self.get_player(id).tax
693+
}
694+
682695
pub fn count_creatures(&self, id: i16) -> i16 {
683696
self.get_player(id).creatures.into_iter().map(|cr| (cr != 0) as i16).sum()
684697
}
@@ -2729,7 +2742,21 @@ impl Game {
27292742
}
27302743
if handlen != usize::MAX {
27312744
self.fx(self.turn, Fx::Sfx(Sfx::mulligan));
2732-
self.drawhand(self.turn, handlen);
2745+
self.drawhand(self.turn, 7);
2746+
let player = self.get_player_mut(self.turn);
2747+
if player.tax < 7 {
2748+
player.tax += 1;
2749+
}
2750+
}
2751+
}
2752+
GameMove::Shuffle(t) => {
2753+
self.remove(t);
2754+
let decklen = self.get_player(self.turn).deck.len() as u32;
2755+
let idx = self.upto(decklen + 1) as usize;
2756+
let mut player = self.get_player_mut(self.turn);
2757+
player.deck_mut().insert(idx, t);
2758+
if player.hand_len() + player.tax as usize == 7 {
2759+
self.r#move(GameMove::Accept);
27332760
}
27342761
}
27352762
GameMove::Foe(t) => {

src/rs/src/skill.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2844,7 +2844,7 @@ impl Skill {
28442844
ctx.fx(t, Fx::Forced);
28452845
ctx.turn = realturn;
28462846
ctx.useactive(t, tgt);
2847-
return
2847+
return;
28482848
}
28492849
}
28502850
ctx.turn = realturn;
@@ -2873,7 +2873,8 @@ impl Skill {
28732873
let mut thing = ctx.get_thing_mut(t);
28742874
if thing.kind == Kind::Creature {
28752875
thing.status.insert(Stat::hp, 1);
2876-
if let Some(amt) = thing.status.get_mut(Stat::maxhp).map(|hp| core::mem::replace(hp, 1)) {
2876+
if let Some(amt) = thing.status.get_mut(Stat::maxhp).map(|hp| core::mem::replace(hp, 1))
2877+
{
28772878
if amt > 0 {
28782879
let maxhp = ctx.get_mut(ctx.get_owner(c), Stat::maxhp);
28792880
*maxhp = (*maxhp + amt - 1).min(500);
@@ -2887,7 +2888,8 @@ impl Skill {
28872888
let mut thing = ctx.get_thing_mut(t);
28882889
if thing.kind == Kind::Creature {
28892890
thing.status.insert(Stat::hp, 1);
2890-
if let Some(amt) = thing.status.get_mut(Stat::maxhp).map(|hp| core::mem::replace(hp, 1)) {
2891+
if let Some(amt) = thing.status.get_mut(Stat::maxhp).map(|hp| core::mem::replace(hp, 1))
2892+
{
28912893
if amt > 0 {
28922894
let maxhp = ctx.get_mut(ctx.get_owner(c), Stat::maxhp);
28932895
*maxhp = (*maxhp + amt - 1).min(500);
@@ -3548,9 +3550,7 @@ impl Skill {
35483550
ctx.set(t, Flag::airborne, true);
35493551
}
35503552
}
3551-
Self::kindle => {
3552-
ctx.incrStatus(c, Stat::dive, 1)
3553-
}
3553+
Self::kindle => ctx.incrStatus(c, Stat::dive, 1),
35543554
Self::lightning => {
35553555
ctx.fx(t, Fx::Lightning);
35563556
ctx.spelldmg(t, 5);
@@ -4388,7 +4388,7 @@ impl Skill {
43884388
let owner = ctx.get_owner(c);
43894389
if ctx.phase == Phase::Mulligan && t == owner {
43904390
ctx.set(ctx.get_foe(owner), Flag::sabbath, true);
4391-
return
4391+
return;
43924392
}
43934393
if !ctx.sanctified(t) {
43944394
ctx.set(t, Flag::sabbath, true);

src/rs/src/text.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,16 @@ impl<'a> SkillThing<'a> {
491491
} else {
492492
"Transform this card into a Fungus"
493493
}),
494-
Skill::gaincharge(x) if ev == Event::Death => Cow::from(format!("Whenever any creature dies, gain {} stack{}", x, if x == 1 { "" } else { "s" })),
495-
Skill::gaincharge(x) if ev == Event::Destroy => Cow::from(format!("Whenever any other permanent is destroyed, gain {} stack{}", x, if x == 1 { "" } else { "s" })),
494+
Skill::gaincharge(x) if ev == Event::Death => Cow::from(format!(
495+
"Whenever any creature dies, gain {} stack{}",
496+
x,
497+
if x == 1 { "" } else { "s" }
498+
)),
499+
Skill::gaincharge(x) if ev == Event::Destroy => Cow::from(format!(
500+
"Whenever any other permanent is destroyed, gain {} stack{}",
501+
x,
502+
if x == 1 { "" } else { "s" }
503+
)),
496504
Skill::gaintimecharge => Cow::from(
497505
"Gain one stack for every card you draw. Does not gain a stack from your draw at the start of your turn",
498506
),

0 commit comments

Comments
 (0)