From 2732daf2a4a21e8639ad9e1d23dd0572824303c7 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Sun, 24 Aug 2025 16:44:14 -0500 Subject: [PATCH 01/14] Duelist number 3 puzzle --- forge-gui/res/puzzle/Duelist_Number_3.pzl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 forge-gui/res/puzzle/Duelist_Number_3.pzl diff --git a/forge-gui/res/puzzle/Duelist_Number_3.pzl b/forge-gui/res/puzzle/Duelist_Number_3.pzl new file mode 100644 index 000000000000..4c05e9ec72d5 --- /dev/null +++ b/forge-gui/res/puzzle/Duelist_Number_3.pzl @@ -0,0 +1,21 @@ +[metadata] +Name:Duelist - thirtysomething +Goal:Win +Turns:1 +Difficulty:Easy +Description:Win this turn. +[state] +ActivePlayer=human +ActivePhase=main1 +HumanLife=1 +AILife=39 +humanhand=Enchantment Alteration; Pyrotechnics; Blood Lust; Fork; Divine Offering; Energy Tap; Shatterstorm; Caverns of Despair +humanprecast= +humangraveyard= +humanlibrary= +humanBattlefield=Celestial Prism; Orcish Oriflamme; Howling Mine; Red Mana Battery|Counters:CHARGE=8; Mox Sapphire; Icy Manipulator; Conservator; Triskelion|Counters:P1P1=3|Id:16; Immolation|AttachedTo:16; Rasputin Dreamweaver|Id:18|Counters:DREAM=7; Gauntlets of Chaos; Wall of Earth; Control Magic|AttachedTo:41; +aibattlefield=Paralyze|AttachedTo:18; Mana Matrix; Abomination|Id:26; Flight|AttachedTo:26; Aladdin's Lamp|Id:28; Animate Artifact|AttachedTo:28; Ichneumon Druid|Id:30; Regeneration|AttachedTo:30; Mesa Pegasus|Id:32; Artifact Ward|AttachedTo:32; Holy Armor|AttachedTo:32; Demonic Hordes; Wall of Stone; Flying Carpet; Nether Void; Birds of Paradise|Tapped; Bayou|Tapped; Savannah|Tapped; Tropical Island|Tapped; Plateau|Tapped; Badlands|Tapped; Scrubland|Tapped; Black Mana Battery|Tapped; Personal Incarnation|Id:41; Spirit Shackle|AttachedTo:41; +aiprecast= +aigraveyard= +ailibrary= +aiexile= From ba49bb3f1b26209e312650c1e2ce3586905646c7 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Sun, 24 Aug 2025 17:00:47 -0500 Subject: [PATCH 02/14] Add #4 Eeny Meeny Miney Moe --- forge-gui/res/puzzle/Duelist_Number_4.pzl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 forge-gui/res/puzzle/Duelist_Number_4.pzl diff --git a/forge-gui/res/puzzle/Duelist_Number_4.pzl b/forge-gui/res/puzzle/Duelist_Number_4.pzl new file mode 100644 index 000000000000..a84fbfd02aab --- /dev/null +++ b/forge-gui/res/puzzle/Duelist_Number_4.pzl @@ -0,0 +1,21 @@ +[metadata] +Name:Duelist #4 - Eeny Meeny Miney Moe +Goal:Win +Turns:1 +Difficulty:Easy +Description:Win this turn. +[state] +ActivePlayer=human +ActivePhase=upkeep +HumanLife=5 +AILife=12 +humanhand=Black Ward; Disenchant; Earthbind; Swords to Plowshares; Instill Energy; Flashfires; Red Elemental Blast; Stone Rain; Giant Growth; Black Knight; +humanprecast= +humangraveyard= +humanlibrary= +humanBattlefield=Plains; Plains; Plains; Plains; Forest; Forest; Mountain; Mountain; Mountain; Mountain; Basalt Monolith; Sol Ring; Rod of Ruin; Stone Giant|Id:1; Dwarven Weaponsmith; Grizzly Bears; Hill Giant; Library of Leng; +aibattlefield=Plains; Plains; Swamp|Tapped; Swamp|Tapped; Swamp|Tapped; Island|Tapped; Island|Tapped; Sol Ring|Tapped; Circle of Protection: Red; Circle of Protection: Green; Phantasmal Forces; Hypnotic Specter|Counters:M1M1=3|Id:11; Unstable Mutation|AttachedTo:11; Pearled Unicorn|Id:10; Flight|AttachedTo:10; Wall of Water; Conversion; Dingus Egg;Weakness|AttachedTo:1; +aiprecast= +aigraveyard= +ailibrary= +aiexile= From 72b6967238d3db1abce309b86119f2c201d6e615 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Sun, 24 Aug 2025 18:01:30 -0500 Subject: [PATCH 03/14] add progress for High on Life puzzle --- .../res/puzzle/Duelist_Number_4_life.pzl | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 forge-gui/res/puzzle/Duelist_Number_4_life.pzl diff --git a/forge-gui/res/puzzle/Duelist_Number_4_life.pzl b/forge-gui/res/puzzle/Duelist_Number_4_life.pzl new file mode 100644 index 000000000000..3f3fafeea0c7 --- /dev/null +++ b/forge-gui/res/puzzle/Duelist_Number_4_life.pzl @@ -0,0 +1,21 @@ +[metadata] +Name:Duelist #4 - High on Life +Goal:Win +Turns:1 +Difficulty:Easy +Description:Win this turn. +[state] +ActivePlayer=human +ActivePhase=untap +HumanLife=93 +AILife=49 +humanhand=Psionic Blast; Flight; Dream Coat; Animate Artifact; Magical Hack; Disenchant; Crumble; +humanprecast= +humangraveyard=Time Walk; War Barge; Blood of the Martyr; Desert Twister; +humanlibrary=Ifh-Biff Efreet; Karma; Recall; +humanBattlefield=Stone Calendar; Stone Calendar; Blue Mana Battery; Tropical Island; Tundra; Tundra; Island; Island; City of Shadows; Hollow Trees|Tapped; Forest; Savannah; City of Brass; Sylvan Library; Time Elemental; Rubinia Soulsinger|Tapped; Serra Angel; Northern Paladin; Ley Druid; Copy Artifact; Fountain of Youth; +aibattlefield=Presence of the Master; Spiritual Sanctuary; Mirror Universe|Tapped; Will O' The Wisp; Wall of Air; Farrelite Priest; Kismet; Millstone|Tapped; Millstone|Tapped; Mox Pearl; Scrubland; Forcefield; Dark Sphere; Island Sanctuary; Urborg|Tapped; Underground Sea|Tapped; Island|Tapped; Black Mana Battery; Svyelunite Temple; The Abyss; Ivory Tower; +aiprecast= +aigraveyard= +ailibrary= +aiexile= From 82783eba2ef8ca4ae01009742e660fa192fbf06f Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Sun, 24 Aug 2025 20:25:00 -0500 Subject: [PATCH 04/14] Solvable thirtysomething --- forge-gui/res/puzzle/Duelist_Number_3.pzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/puzzle/Duelist_Number_3.pzl b/forge-gui/res/puzzle/Duelist_Number_3.pzl index 4c05e9ec72d5..61e59fc537a5 100644 --- a/forge-gui/res/puzzle/Duelist_Number_3.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_3.pzl @@ -1,5 +1,5 @@ [metadata] -Name:Duelist - thirtysomething +Name:Duelist - thirtysomething (modified) Goal:Win Turns:1 Difficulty:Easy @@ -13,8 +13,8 @@ humanhand=Enchantment Alteration; Pyrotechnics; Blood Lust; Fork; Divine Offerin humanprecast= humangraveyard= humanlibrary= -humanBattlefield=Celestial Prism; Orcish Oriflamme; Howling Mine; Red Mana Battery|Counters:CHARGE=8; Mox Sapphire; Icy Manipulator; Conservator; Triskelion|Counters:P1P1=3|Id:16; Immolation|AttachedTo:16; Rasputin Dreamweaver|Id:18|Counters:DREAM=7; Gauntlets of Chaos; Wall of Earth; Control Magic|AttachedTo:41; -aibattlefield=Paralyze|AttachedTo:18; Mana Matrix; Abomination|Id:26; Flight|AttachedTo:26; Aladdin's Lamp|Id:28; Animate Artifact|AttachedTo:28; Ichneumon Druid|Id:30; Regeneration|AttachedTo:30; Mesa Pegasus|Id:32; Artifact Ward|AttachedTo:32; Holy Armor|AttachedTo:32; Demonic Hordes; Wall of Stone; Flying Carpet; Nether Void; Birds of Paradise|Tapped; Bayou|Tapped; Savannah|Tapped; Tropical Island|Tapped; Plateau|Tapped; Badlands|Tapped; Scrubland|Tapped; Black Mana Battery|Tapped; Personal Incarnation|Id:41; Spirit Shackle|AttachedTo:41; +humanBattlefield=Celestial Prism; Orcish Oriflamme; Howling Mine; Red Mana Battery|Counters:CHARGE=10; Mox Sapphire; Icy Manipulator; Conservator; Triskelion|Counters:P1P1=3,P0P1=1|Id:16; Immolation|AttachedTo:16; Rasputin Dreamweaver|Id:18|Counters:DREAM=6; Gauntlets of Chaos; Wall of Earth; Control Magic|AttachedTo:41; +aibattlefield=Paralyze|AttachedTo:18; Mana Matrix; Abomination|Id:26; Flight|AttachedTo:26; Aladdin's Lamp|Id:28; Animate Artifact|AttachedTo:28; Ichneumon Druid|Id:30; Regeneration|AttachedTo:30; Mesa Pegasus|Id:32; Artifact Ward|AttachedTo:32; Holy Armor|AttachedTo:32; Demonic Hordes; Wall of Stone; Flying Carpet; Nether Void; Birds of Paradise|Tapped; Bayou|Tapped; Savannah|Tapped; Tropical Island|Tapped; Plateau|Tapped; Badlands|Tapped; Scrubland|Tapped; Black Mana Battery|Tapped; Personal Incarnation|Counters:M0M2=2|Id:41; Spirit Shackle|AttachedTo:41; aiprecast= aigraveyard= ailibrary= From 75bbd118b768920a291ef1051494dd70438484bb Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 29 Aug 2025 13:49:14 -0500 Subject: [PATCH 05/14] progress --- forge-ai/src/main/java/forge/ai/GameState.java | 6 ++++++ forge-gui/res/puzzle/Duelist_Number_3.pzl | 7 ++++--- forge-gui/res/puzzle/Duelist_Number_4.pzl | 2 +- forge-gui/res/puzzle/Duelist_Number_4_life.pzl | 2 +- forge-gui/res/puzzle/duelist_puzzle_notes.txt | 10 ++++++++++ 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 forge-gui/res/puzzle/duelist_puzzle_notes.txt diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 5aeb1e8915cd..a8afc0088b21 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -66,6 +66,7 @@ static class PlayerState { private String precast = null; private String putOnStack = null; private final Map cardTexts = new EnumMap<>(ZoneType.class); + private String name = null; } private final List playerStates = new ArrayList<>(); @@ -577,6 +578,8 @@ else if (categoryName.endsWith("phaseadvance")) getPlayerState(categoryName).manaPool = categoryValue; } else if (categoryName.endsWith("persistentmana")) { getPlayerState(categoryName).persistentMana = categoryValue; + } else if (categoryName.endsWith("name")) { + getPlayerState(categoryName).name = categoryValue; } else { System.err.println("Unknown key: " + categoryName); } @@ -1137,6 +1140,9 @@ private void applyCountersToGameEntity(GameEntity entity, String counterString) private void setupPlayerState(final Player p, final PlayerState state) { // Lock check static as we setup player state + if (state.name != null && !state.name.isEmpty()) { + p.setName(state.name); + } // Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up // before setting up a new state diff --git a/forge-gui/res/puzzle/Duelist_Number_3.pzl b/forge-gui/res/puzzle/Duelist_Number_3.pzl index 61e59fc537a5..4baea3065ff2 100644 --- a/forge-gui/res/puzzle/Duelist_Number_3.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_3.pzl @@ -1,14 +1,15 @@ [metadata] -Name:Duelist - thirtysomething (modified) +Name:Magic the Puzzling - Duelist #3 - thirtysomething (modified) Goal:Win Turns:1 -Difficulty:Easy -Description:Win this turn. +Difficulty:Hard +Description:Your opponent is David. He hit you early, he hit you fast, and he hit you hard. Luckily, you were able to buy yourself a little time with a few blockers and an Icy Manipulator. David, unfortunately, has made good use of this downtime to destroy all your land (mostly thanks to his Demonic Hordes) and heal himself to a rather comfortable life total. Then last turn, David finally got the edge he needed. Flight, Flying Carpet, and a Mesa Pegasus gave him the ability to fly. Festival put off the inevitable for another turn, but since your Icy Manipulator can only take care of one flier and you have nothing else to block with, David has you next turn. All seemed lost until you drew a Fork, and then you saw the answer. It's the beginning of your main phase. Finish David off this turn without dying. [state] ActivePlayer=human ActivePhase=main1 HumanLife=1 AILife=39 +ainame=David humanhand=Enchantment Alteration; Pyrotechnics; Blood Lust; Fork; Divine Offering; Energy Tap; Shatterstorm; Caverns of Despair humanprecast= humangraveyard= diff --git a/forge-gui/res/puzzle/Duelist_Number_4.pzl b/forge-gui/res/puzzle/Duelist_Number_4.pzl index a84fbfd02aab..0a08c6a7dd9a 100644 --- a/forge-gui/res/puzzle/Duelist_Number_4.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_4.pzl @@ -1,5 +1,5 @@ [metadata] -Name:Duelist #4 - Eeny Meeny Miney Moe +Name:Magic the Puzzling - Duelist #4 - Eeny Meeny Miney Moe Goal:Win Turns:1 Difficulty:Easy diff --git a/forge-gui/res/puzzle/Duelist_Number_4_life.pzl b/forge-gui/res/puzzle/Duelist_Number_4_life.pzl index 3f3fafeea0c7..8e2c6b0571d9 100644 --- a/forge-gui/res/puzzle/Duelist_Number_4_life.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_4_life.pzl @@ -1,5 +1,5 @@ [metadata] -Name:Duelist #4 - High on Life +Name:Magic the Puzzling - Duelist #4 - High on Life Goal:Win Turns:1 Difficulty:Easy diff --git a/forge-gui/res/puzzle/duelist_puzzle_notes.txt b/forge-gui/res/puzzle/duelist_puzzle_notes.txt new file mode 100644 index 000000000000..782bf46ef7e8 --- /dev/null +++ b/forge-gui/res/puzzle/duelist_puzzle_notes.txt @@ -0,0 +1,10 @@ +Duelist #3 +------------------- +"Sitting on Defense" - survive opponents turn with cards provided (not implemented), I think goal = "survive" +"thirtysomething" - Win this turn. + - solution doesn't work with current rules + - modifications: Red Mana Battery 8 counters -> 10 counters, Rasputin - 7 counters -> 6 counters, Triskelion - add +0/+1 counter (could have been from Dwarven Weaponsmith back in the day) + - Tested in this configuration and confirmed working. + +Duelist #4 +------------------- \ No newline at end of file From 8b1f3b4546d71ede1a0d3a0cc8bae321f0eb3366 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:11:43 -0500 Subject: [PATCH 06/14] rename the puzzle files --- .../puzzle/{Duelist_Number_4.pzl => Duelist_Number_5.pzl} | 5 +++-- .../{Duelist_Number_4_life.pzl => Duelist_Number_5_life.pzl} | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) rename forge-gui/res/puzzle/{Duelist_Number_4.pzl => Duelist_Number_5.pzl} (62%) rename forge-gui/res/puzzle/{Duelist_Number_4_life.pzl => Duelist_Number_5_life.pzl} (67%) diff --git a/forge-gui/res/puzzle/Duelist_Number_4.pzl b/forge-gui/res/puzzle/Duelist_Number_5.pzl similarity index 62% rename from forge-gui/res/puzzle/Duelist_Number_4.pzl rename to forge-gui/res/puzzle/Duelist_Number_5.pzl index 0a08c6a7dd9a..a3cb27db46f3 100644 --- a/forge-gui/res/puzzle/Duelist_Number_4.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_5.pzl @@ -1,9 +1,9 @@ [metadata] -Name:Magic the Puzzling - Duelist #4 - Eeny Meeny Miney Moe +Name:Magic the Puzzling - Duelist #5 - Eeny Meeny Miney Moe Goal:Win Turns:1 Difficulty:Easy -Description:Win this turn. +Description:Your opponent is Nick. He's under the false assumption that he's going to win this duel. Of source, with Circles of Protection for two of your three colors, four blockers, and a Conversion in play, he has plenty of reason to be cocky. But then, you draw an Instill Energy and the solution comes to you. You're going to take Nick down this turn and you plan to enjoy the look on his face when you do it.\n\nIt is the beginning of your upkeep phase. Defeat Nick this turn without dying. [state] ActivePlayer=human ActivePhase=upkeep @@ -19,3 +19,4 @@ aiprecast= aigraveyard= ailibrary= aiexile= +aiName=Nick diff --git a/forge-gui/res/puzzle/Duelist_Number_4_life.pzl b/forge-gui/res/puzzle/Duelist_Number_5_life.pzl similarity index 67% rename from forge-gui/res/puzzle/Duelist_Number_4_life.pzl rename to forge-gui/res/puzzle/Duelist_Number_5_life.pzl index 8e2c6b0571d9..243d22b43ea1 100644 --- a/forge-gui/res/puzzle/Duelist_Number_4_life.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_5_life.pzl @@ -1,9 +1,9 @@ [metadata] -Name:Magic the Puzzling - Duelist #4 - High on Life +Name:Magic the Puzzling - Duelist #5 - High on Life Goal:Win Turns:1 Difficulty:Easy -Description:Win this turn. +Description:Your opponent is Thierry. He's playing a Millstone desck. So even though you've managed to acquire a great deal of life, all that won't matter a turn from now after Thierry has burned your last few cards. But never one to give up a fight, you've decided to play this duel to the very end.\n\nIt is now the beginning of your turn (before your untap phase). Defeat Thierry before he can draw another card. Ph, and try not to die. [state] ActivePlayer=human ActivePhase=untap @@ -19,3 +19,4 @@ aiprecast= aigraveyard= ailibrary= aiexile= +aiName=Thierry From 9d431416819d7a2b6c1214035750972b6cd3fe09 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:11:59 -0500 Subject: [PATCH 07/14] allow setting of the AI name --- .../src/main/java/forge/gamemodes/puzzle/Puzzle.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java b/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java index 9916be959ba0..90bccdee9da9 100644 --- a/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java +++ b/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java @@ -29,6 +29,7 @@ public class Puzzle extends GameState implements InventoryItem, Comparable metadataLines) { this.difficulty = split[1].trim(); } else if ("Description".equalsIgnoreCase(split[0])) { this.description = split[1].trim(); + } else if ("AiName".equalsIgnoreCase(split[0])) { + this.aiName = split[1].trim(); } else if ("Targets".equalsIgnoreCase(split[0])) { this.targets = split[1].trim(); } else if ("TargetCount".equalsIgnoreCase(split[0])) { @@ -94,6 +97,11 @@ public String getGoalDescription() { desc.append("\nTurns Limit: "); desc.append(this.turns); + if (this.aiName != null) { + desc.append("\nOpponent: "); + desc.append(this.aiName); + } + if (this.description != null) { desc.append("\n\n"); desc.append(this.description.replace("\\n", "\n")); @@ -247,6 +255,8 @@ public String getName() { public boolean getCompleted() { return completed; } + public String getAiName() { return aiName; } + public String toString() { StringBuilder sb = new StringBuilder(); if (this.completed) { From 29d7ac8e5a67f4e36e5054791bf262171f82c3fb Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:16:23 -0500 Subject: [PATCH 08/14] add the sets --- forge-gui/res/puzzle/Duelist_Number_5.pzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/puzzle/Duelist_Number_5.pzl b/forge-gui/res/puzzle/Duelist_Number_5.pzl index a3cb27db46f3..077af400b3e0 100644 --- a/forge-gui/res/puzzle/Duelist_Number_5.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_5.pzl @@ -2,19 +2,19 @@ Name:Magic the Puzzling - Duelist #5 - Eeny Meeny Miney Moe Goal:Win Turns:1 -Difficulty:Easy +Difficulty:Common Description:Your opponent is Nick. He's under the false assumption that he's going to win this duel. Of source, with Circles of Protection for two of your three colors, four blockers, and a Conversion in play, he has plenty of reason to be cocky. But then, you draw an Instill Energy and the solution comes to you. You're going to take Nick down this turn and you plan to enjoy the look on his face when you do it.\n\nIt is the beginning of your upkeep phase. Defeat Nick this turn without dying. [state] ActivePlayer=human ActivePhase=upkeep HumanLife=5 AILife=12 -humanhand=Black Ward; Disenchant; Earthbind; Swords to Plowshares; Instill Energy; Flashfires; Red Elemental Blast; Stone Rain; Giant Growth; Black Knight; +humanhand=Black Ward|Set:3ED; Disenchant|Set:3ED; Earthbind|Set:3ED; Swords to Plowshares|Set:3ED; Instill Energy|Set:3ED; Flashfires|Set:3ED; Red Elemental Blast|Set:3ED; Stone Rain|Set:3ED; Giant Growth|Set:3ED; Black Knight|Set:3ED; humanprecast= humangraveyard= humanlibrary= -humanBattlefield=Plains; Plains; Plains; Plains; Forest; Forest; Mountain; Mountain; Mountain; Mountain; Basalt Monolith; Sol Ring; Rod of Ruin; Stone Giant|Id:1; Dwarven Weaponsmith; Grizzly Bears; Hill Giant; Library of Leng; -aibattlefield=Plains; Plains; Swamp|Tapped; Swamp|Tapped; Swamp|Tapped; Island|Tapped; Island|Tapped; Sol Ring|Tapped; Circle of Protection: Red; Circle of Protection: Green; Phantasmal Forces; Hypnotic Specter|Counters:M1M1=3|Id:11; Unstable Mutation|AttachedTo:11; Pearled Unicorn|Id:10; Flight|AttachedTo:10; Wall of Water; Conversion; Dingus Egg;Weakness|AttachedTo:1; +humanBattlefield=Plains|Set:3ED; Plains|Set:3ED; Plains|Set:3ED; Plains|Set:3ED; Forest|Set:3ED; Forest|Set:3ED; Mountain|Set:3ED; Mountain|Set:3ED; Mountain|Set:3ED; Mountain|Set:3ED; Basalt Monolith|Set:3ED; Sol Ring|Set:3ED; Rod of Ruin|Set:3ED; Stone Giant|Id:1|Set:3ED; Dwarven Weaponsmith|Set:3ED; Grizzly Bears|Set:3ED; Hill Giant|Set:3ED; Library of Leng|Set:3ED; +aibattlefield=Plains|Set:3ED; Plains|Set:3ED; Swamp|Tapped|Set:3ED; Swamp|Tapped|Set:3ED; Swamp|Tapped|Set:3ED; Island|Tapped|Set:3ED; Island|Tapped|Set:3ED; Sol Ring|Tapped|Set:3ED; Circle of Protection: Red|Set:3ED; Circle of Protection: Green|Set:3ED; Phantasmal Forces|Set:3ED; Hypnotic Specter|Counters:M1M1=3|Id:11|Set:3ED; Unstable Mutation|AttachedTo:11|Set:3ED; Pearled Unicorn|Id:10|Set:3ED; Flight|AttachedTo:10|Set:3ED; Wall of Water|Set:3ED; Conversion|Set:3ED; Dingus Egg|Set:3ED;Weakness|AttachedTo:1|Set:3ED; aiprecast= aigraveyard= ailibrary= From 5dbe0d362e0d940765845a9d472102c5efa08b3f Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:21:25 -0500 Subject: [PATCH 09/14] aiName -> AIName --- forge-gui/res/puzzle/Duelist_Number_5.pzl | 2 +- forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/puzzle/Duelist_Number_5.pzl b/forge-gui/res/puzzle/Duelist_Number_5.pzl index 077af400b3e0..3c353c544c6d 100644 --- a/forge-gui/res/puzzle/Duelist_Number_5.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_5.pzl @@ -9,6 +9,7 @@ ActivePlayer=human ActivePhase=upkeep HumanLife=5 AILife=12 +AIName=Nick humanhand=Black Ward|Set:3ED; Disenchant|Set:3ED; Earthbind|Set:3ED; Swords to Plowshares|Set:3ED; Instill Energy|Set:3ED; Flashfires|Set:3ED; Red Elemental Blast|Set:3ED; Stone Rain|Set:3ED; Giant Growth|Set:3ED; Black Knight|Set:3ED; humanprecast= humangraveyard= @@ -19,4 +20,3 @@ aiprecast= aigraveyard= ailibrary= aiexile= -aiName=Nick diff --git a/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java b/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java index 90bccdee9da9..60693fa671b6 100644 --- a/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java +++ b/forge-gui/src/main/java/forge/gamemodes/puzzle/Puzzle.java @@ -63,7 +63,7 @@ private void loadMetaData(List metadataLines) { this.difficulty = split[1].trim(); } else if ("Description".equalsIgnoreCase(split[0])) { this.description = split[1].trim(); - } else if ("AiName".equalsIgnoreCase(split[0])) { + } else if ("AIName".equalsIgnoreCase(split[0])) { this.aiName = split[1].trim(); } else if ("Targets".equalsIgnoreCase(split[0])) { this.targets = split[1].trim(); @@ -255,7 +255,7 @@ public String getName() { public boolean getCompleted() { return completed; } - public String getAiName() { return aiName; } + public String getAIName() { return aiName; } public String toString() { StringBuilder sb = new StringBuilder(); From bf27cdd5ac4d4fdc69e9359524dffedf2bda1d46 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:22:43 -0500 Subject: [PATCH 10/14] remove other puzzles --- forge-gui/res/puzzle/Duelist_Number_3.pzl | 22 ------------------- .../res/puzzle/Duelist_Number_5_life.pzl | 22 ------------------- forge-gui/res/puzzle/duelist_puzzle_notes.txt | 10 --------- 3 files changed, 54 deletions(-) delete mode 100644 forge-gui/res/puzzle/Duelist_Number_3.pzl delete mode 100644 forge-gui/res/puzzle/Duelist_Number_5_life.pzl delete mode 100644 forge-gui/res/puzzle/duelist_puzzle_notes.txt diff --git a/forge-gui/res/puzzle/Duelist_Number_3.pzl b/forge-gui/res/puzzle/Duelist_Number_3.pzl deleted file mode 100644 index 4baea3065ff2..000000000000 --- a/forge-gui/res/puzzle/Duelist_Number_3.pzl +++ /dev/null @@ -1,22 +0,0 @@ -[metadata] -Name:Magic the Puzzling - Duelist #3 - thirtysomething (modified) -Goal:Win -Turns:1 -Difficulty:Hard -Description:Your opponent is David. He hit you early, he hit you fast, and he hit you hard. Luckily, you were able to buy yourself a little time with a few blockers and an Icy Manipulator. David, unfortunately, has made good use of this downtime to destroy all your land (mostly thanks to his Demonic Hordes) and heal himself to a rather comfortable life total. Then last turn, David finally got the edge he needed. Flight, Flying Carpet, and a Mesa Pegasus gave him the ability to fly. Festival put off the inevitable for another turn, but since your Icy Manipulator can only take care of one flier and you have nothing else to block with, David has you next turn. All seemed lost until you drew a Fork, and then you saw the answer. It's the beginning of your main phase. Finish David off this turn without dying. -[state] -ActivePlayer=human -ActivePhase=main1 -HumanLife=1 -AILife=39 -ainame=David -humanhand=Enchantment Alteration; Pyrotechnics; Blood Lust; Fork; Divine Offering; Energy Tap; Shatterstorm; Caverns of Despair -humanprecast= -humangraveyard= -humanlibrary= -humanBattlefield=Celestial Prism; Orcish Oriflamme; Howling Mine; Red Mana Battery|Counters:CHARGE=10; Mox Sapphire; Icy Manipulator; Conservator; Triskelion|Counters:P1P1=3,P0P1=1|Id:16; Immolation|AttachedTo:16; Rasputin Dreamweaver|Id:18|Counters:DREAM=6; Gauntlets of Chaos; Wall of Earth; Control Magic|AttachedTo:41; -aibattlefield=Paralyze|AttachedTo:18; Mana Matrix; Abomination|Id:26; Flight|AttachedTo:26; Aladdin's Lamp|Id:28; Animate Artifact|AttachedTo:28; Ichneumon Druid|Id:30; Regeneration|AttachedTo:30; Mesa Pegasus|Id:32; Artifact Ward|AttachedTo:32; Holy Armor|AttachedTo:32; Demonic Hordes; Wall of Stone; Flying Carpet; Nether Void; Birds of Paradise|Tapped; Bayou|Tapped; Savannah|Tapped; Tropical Island|Tapped; Plateau|Tapped; Badlands|Tapped; Scrubland|Tapped; Black Mana Battery|Tapped; Personal Incarnation|Counters:M0M2=2|Id:41; Spirit Shackle|AttachedTo:41; -aiprecast= -aigraveyard= -ailibrary= -aiexile= diff --git a/forge-gui/res/puzzle/Duelist_Number_5_life.pzl b/forge-gui/res/puzzle/Duelist_Number_5_life.pzl deleted file mode 100644 index 243d22b43ea1..000000000000 --- a/forge-gui/res/puzzle/Duelist_Number_5_life.pzl +++ /dev/null @@ -1,22 +0,0 @@ -[metadata] -Name:Magic the Puzzling - Duelist #5 - High on Life -Goal:Win -Turns:1 -Difficulty:Easy -Description:Your opponent is Thierry. He's playing a Millstone desck. So even though you've managed to acquire a great deal of life, all that won't matter a turn from now after Thierry has burned your last few cards. But never one to give up a fight, you've decided to play this duel to the very end.\n\nIt is now the beginning of your turn (before your untap phase). Defeat Thierry before he can draw another card. Ph, and try not to die. -[state] -ActivePlayer=human -ActivePhase=untap -HumanLife=93 -AILife=49 -humanhand=Psionic Blast; Flight; Dream Coat; Animate Artifact; Magical Hack; Disenchant; Crumble; -humanprecast= -humangraveyard=Time Walk; War Barge; Blood of the Martyr; Desert Twister; -humanlibrary=Ifh-Biff Efreet; Karma; Recall; -humanBattlefield=Stone Calendar; Stone Calendar; Blue Mana Battery; Tropical Island; Tundra; Tundra; Island; Island; City of Shadows; Hollow Trees|Tapped; Forest; Savannah; City of Brass; Sylvan Library; Time Elemental; Rubinia Soulsinger|Tapped; Serra Angel; Northern Paladin; Ley Druid; Copy Artifact; Fountain of Youth; -aibattlefield=Presence of the Master; Spiritual Sanctuary; Mirror Universe|Tapped; Will O' The Wisp; Wall of Air; Farrelite Priest; Kismet; Millstone|Tapped; Millstone|Tapped; Mox Pearl; Scrubland; Forcefield; Dark Sphere; Island Sanctuary; Urborg|Tapped; Underground Sea|Tapped; Island|Tapped; Black Mana Battery; Svyelunite Temple; The Abyss; Ivory Tower; -aiprecast= -aigraveyard= -ailibrary= -aiexile= -aiName=Thierry diff --git a/forge-gui/res/puzzle/duelist_puzzle_notes.txt b/forge-gui/res/puzzle/duelist_puzzle_notes.txt deleted file mode 100644 index 782bf46ef7e8..000000000000 --- a/forge-gui/res/puzzle/duelist_puzzle_notes.txt +++ /dev/null @@ -1,10 +0,0 @@ -Duelist #3 -------------------- -"Sitting on Defense" - survive opponents turn with cards provided (not implemented), I think goal = "survive" -"thirtysomething" - Win this turn. - - solution doesn't work with current rules - - modifications: Red Mana Battery 8 counters -> 10 counters, Rasputin - 7 counters -> 6 counters, Triskelion - add +0/+1 counter (could have been from Dwarven Weaponsmith back in the day) - - Tested in this configuration and confirmed working. - -Duelist #4 -------------------- \ No newline at end of file From 957aa05a4d1f0f4e2ca2b860709389f6e7e6db5f Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:25:42 -0500 Subject: [PATCH 11/14] revert GameState --- forge-game/src/main/java/forge/game/GameState.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameState.java b/forge-game/src/main/java/forge/game/GameState.java index 11bdff2456dc..59b96fa9b87a 100644 --- a/forge-game/src/main/java/forge/game/GameState.java +++ b/forge-game/src/main/java/forge/game/GameState.java @@ -66,7 +66,6 @@ static class PlayerState { private String precast = null; private String putOnStack = null; private final Map cardTexts = new EnumMap<>(ZoneType.class); - private String name = null; } private final List playerStates = new ArrayList<>(); @@ -575,8 +574,6 @@ else if (categoryName.endsWith("phaseadvance")) getPlayerState(categoryName).manaPool = categoryValue; } else if (categoryName.endsWith("persistentmana")) { getPlayerState(categoryName).persistentMana = categoryValue; - } else if (categoryName.endsWith("name")) { - getPlayerState(categoryName).name = categoryValue; } else { System.err.println("Unknown key: " + categoryName); } @@ -1139,9 +1136,6 @@ private void applyCountersToGameEntity(GameEntity entity, String counterString) private void setupPlayerState(final Player p, final PlayerState state) { // Lock check static as we setup player state - if (state.name != null && !state.name.isEmpty()) { - p.setName(state.name); - } // Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up // before setting up a new state From eba88cfc020b97e6bd8c57bd2abecfb8a1f2f398 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 18:27:21 -0500 Subject: [PATCH 12/14] Revert "revert GameState" This reverts commit 957aa05a4d1f0f4e2ca2b860709389f6e7e6db5f. --- forge-game/src/main/java/forge/game/GameState.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/forge-game/src/main/java/forge/game/GameState.java b/forge-game/src/main/java/forge/game/GameState.java index 59b96fa9b87a..11bdff2456dc 100644 --- a/forge-game/src/main/java/forge/game/GameState.java +++ b/forge-game/src/main/java/forge/game/GameState.java @@ -66,6 +66,7 @@ static class PlayerState { private String precast = null; private String putOnStack = null; private final Map cardTexts = new EnumMap<>(ZoneType.class); + private String name = null; } private final List playerStates = new ArrayList<>(); @@ -574,6 +575,8 @@ else if (categoryName.endsWith("phaseadvance")) getPlayerState(categoryName).manaPool = categoryValue; } else if (categoryName.endsWith("persistentmana")) { getPlayerState(categoryName).persistentMana = categoryValue; + } else if (categoryName.endsWith("name")) { + getPlayerState(categoryName).name = categoryValue; } else { System.err.println("Unknown key: " + categoryName); } @@ -1136,6 +1139,9 @@ private void applyCountersToGameEntity(GameEntity entity, String counterString) private void setupPlayerState(final Player p, final PlayerState state) { // Lock check static as we setup player state + if (state.name != null && !state.name.isEmpty()) { + p.setName(state.name); + } // Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up // before setting up a new state From 204b78043dd6ea1c4f8f5840d21fdd1d3ca305f8 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Fri, 14 Aug 2026 19:20:49 -0500 Subject: [PATCH 13/14] allow setting the match title --- .../src/main/java/forge/game/GameView.java | 3 +++ .../src/main/java/forge/game/Match.java | 5 +++- .../home/puzzle/CSubmenuPuzzleCreate.java | 1 + .../home/puzzle/CSubmenuPuzzleSolve.java | 1 + .../screens/home/puzzle/CSubmenuTutorial.java | 1 + .../screens/home/puzzle/PuzzleScreen.java | 5 +++- .../forge/gamemodes/match/HostedMatch.java | 27 +++++++++++++++++++ 7 files changed, 41 insertions(+), 2 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameView.java b/forge-game/src/main/java/forge/game/GameView.java index 3a699a7008e3..d0578f3cb7f9 100644 --- a/forge-game/src/main/java/forge/game/GameView.java +++ b/forge-game/src/main/java/forge/game/GameView.java @@ -72,6 +72,9 @@ public void updatePlayers(final Game game) { public String getTitle() { return get(TrackableProperty.Title); } + public void updateTitle() { + set(TrackableProperty.Title, match.getTitle()); + } public boolean isCommander() { return get(TrackableProperty.IsCommander); diff --git a/forge-game/src/main/java/forge/game/Match.java b/forge-game/src/main/java/forge/game/Match.java index dc9efb22da71..24c676582cdf 100644 --- a/forge-game/src/main/java/forge/game/Match.java +++ b/forge-game/src/main/java/forge/game/Match.java @@ -33,7 +33,7 @@ public class Match { private List removedCards = Lists.newArrayList(); private final List players; private final GameRules rules; - private final String title; + private String title; private final EventBus events = new EventBus("match events"); private final Map gameOutcomes = Maps.newHashMap(); @@ -49,6 +49,9 @@ public Match(final GameRules rules0, final List players0, fina public GameRules getRules() { return rules; } + public void setTitle(final String title) { + this.title = title; + } String getTitle() { final Multiset wins = getGamesWon(); final StringBuilder titleAppend = new StringBuilder(title); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleCreate.java b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleCreate.java index aed0f3336d07..256a438b3026 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleCreate.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleCreate.java @@ -93,6 +93,7 @@ private void startPuzzleCreate() { SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblWelcomePuzzleModeMessage"), Localizer.getInstance().getMessage("lblCreateNewPuzzle"), SOptionPane.WARNING_ICON); emptyPuzzle.applyToGame(hostedMatch.getGame()); + hostedMatch.refreshTitle(); }); final List players = new ArrayList<>(); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java index c9a9f387a98f..5919e295b621 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java @@ -89,6 +89,7 @@ private boolean startPuzzleSolve() { hostedMatch.setStartGameHook(() -> { SOptionPane.showMessageDialog(selected.getGoalDescription(), selected.getName(), SOptionPane.INFORMATION_ICON); selected.applyToGame(hostedMatch.getGame()); + hostedMatch.refreshTitle(); }); hostedMatch.setEndGameHook((() -> selected.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer())))); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java index 3fd9e3fabfb4..6b6ec36761ff 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuTutorial.java @@ -89,6 +89,7 @@ private boolean startPuzzleSolve() { hostedMatch.setStartGameHook(() -> { SOptionPane.showMessageDialog(selected.getGoalDescription(), selected.getName(), SOptionPane.INFORMATION_ICON); selected.applyToGame(hostedMatch.getGame()); + hostedMatch.refreshTitle(); }); hostedMatch.setEndGameHook((() -> selected.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer())))); diff --git a/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java b/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java index e318b0b1c922..ab6e00e83e87 100644 --- a/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java +++ b/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java @@ -60,7 +60,10 @@ protected void startMatch() { LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingThePuzzle"), true, () -> { // Load selected puzzle final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); - hostedMatch.setStartGameHook(() -> chosen.applyToGame(hostedMatch.getGame())); + hostedMatch.setStartGameHook(() -> { + chosen.applyToGame(hostedMatch.getGame()); + hostedMatch.refreshTitle(); + }); hostedMatch.setEndGameHook((() -> chosen.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer())))); diff --git a/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java b/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java index 2fffd23b1168..a73d67827475 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java @@ -366,6 +366,33 @@ public GameView getGameView() { return game == null ? null : game.getView(); } + // Recomputes the "X vs Y" match title from the players' current in-game names + // and re-pushes it to any open guis. Needed after a puzzle renames a player + // (e.g. via AIName) since the title is normally captured once at match start, + // before the puzzle's GameState has been applied. + public void refreshTitle() { + if (game == null || match == null) { + return; + } + final FCollectionView players = game.getPlayers(); + if (players.size() != 2) { + return; + } + match.setTitle(TextUtil.concatNoSpace(players.get(0).getName(), " vs ", players.get(1).getName())); + + final GameView gameView = getGameView(); + if (gameView == null) { + return; + } + gameView.updateTitle(); + + FThreads.invokeInEdtNowOrLater(() -> { + for (final IGuiGame gui : guis.values()) { + gui.setGameView(gameView); + } + }); + } + public void endCurrentGame() { if (game == null) { return; } boolean isMatchOver = game.getView().isMatchOver(); From 7920da264cabc185522b8a21ee4cebbcac592009 Mon Sep 17 00:00:00 2001 From: "Dennis R. Friedrichsen" Date: Sun, 16 Aug 2026 09:54:27 -0500 Subject: [PATCH 14/14] fix typo in description --- forge-gui/res/puzzle/Duelist_Number_5.pzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/puzzle/Duelist_Number_5.pzl b/forge-gui/res/puzzle/Duelist_Number_5.pzl index 3c353c544c6d..446dc08050b9 100644 --- a/forge-gui/res/puzzle/Duelist_Number_5.pzl +++ b/forge-gui/res/puzzle/Duelist_Number_5.pzl @@ -3,7 +3,7 @@ Name:Magic the Puzzling - Duelist #5 - Eeny Meeny Miney Moe Goal:Win Turns:1 Difficulty:Common -Description:Your opponent is Nick. He's under the false assumption that he's going to win this duel. Of source, with Circles of Protection for two of your three colors, four blockers, and a Conversion in play, he has plenty of reason to be cocky. But then, you draw an Instill Energy and the solution comes to you. You're going to take Nick down this turn and you plan to enjoy the look on his face when you do it.\n\nIt is the beginning of your upkeep phase. Defeat Nick this turn without dying. +Description:Your opponent is Nick. He's under the false assumption that he's going to win this duel. Of course, with Circles of Protection for two of your three colors, four blockers, and a Conversion in play, he has plenty of reason to be cocky. But then, you draw an Instill Energy and the solution comes to you. You're going to take Nick down this turn and you plan to enjoy the look on his face when you do it.\n\nIt is the beginning of your upkeep phase. Defeat Nick this turn without dying. [state] ActivePlayer=human ActivePhase=upkeep