Skip to content

Commit ad0fee8

Browse files
committed
feat(werewolf): sheriff election system &tts& role action enhancements
1 parent 198da7f commit ad0fee8

26 files changed

Lines changed: 4217 additions & 847 deletions

agentscope-core/src/main/java/io/agentscope/core/agent/StructuredOutputCapableAgent.java

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,24 @@
4444
/**
4545
* Abstract base class for agents that support structured output generation.
4646
*
47-
* <p>This class provides the infrastructure for generating structured output using the
48-
* {@code generate_response} tool pattern combined with StructuredOutputHook for flow control.
47+
* <p>This class provides the infrastructure for generating structured output using the {@code
48+
* generate_response} tool pattern combined with StructuredOutputHook for flow control.
4949
*
5050
* <p><b>Key Features:</b>
51+
*
5152
* <ul>
52-
* <li>Automatic tool registration for structured output</li>
53-
* <li>Schema validation before tool execution</li>
54-
* <li>Memory compression after structured output completion</li>
55-
* <li>Configurable reminder mode (TOOL_CHOICE or PROMPT)</li>
53+
* <li>Automatic tool registration for structured output
54+
* <li>Schema validation before tool execution
55+
* <li>Memory compression after structured output completion
56+
* <li>Configurable reminder mode (TOOL_CHOICE or PROMPT)
5657
* </ul>
5758
*
5859
* <p><b>Subclass Requirements:</b>
60+
*
5961
* <ul>
60-
* <li>Provide Toolkit via constructor</li>
61-
* <li>Implement {@link #getMemory()} for memory access</li>
62-
* <li>Implement {@link #buildGenerateOptions()} for model options</li>
62+
* <li>Provide Toolkit via constructor
63+
* <li>Implement {@link #getMemory()} for memory access
64+
* <li>Implement {@link #buildGenerateOptions()} for model options
6365
* </ul>
6466
*/
6567
public abstract class StructuredOutputCapableAgent extends AgentBase {
@@ -72,9 +74,7 @@ public abstract class StructuredOutputCapableAgent extends AgentBase {
7274
protected final Toolkit toolkit;
7375
protected final StructuredOutputReminder structuredOutputReminder;
7476

75-
/**
76-
* Constructor with default reminder mode (TOOL_CHOICE).
77-
*/
77+
/** Constructor with default reminder mode (TOOL_CHOICE). */
7878
protected StructuredOutputCapableAgent(
7979
String name,
8080
String description,
@@ -84,9 +84,7 @@ protected StructuredOutputCapableAgent(
8484
this(name, description, checkRunning, hooks, toolkit, StructuredOutputReminder.TOOL_CHOICE);
8585
}
8686

87-
/**
88-
* Constructor with custom reminder mode.
89-
*/
87+
/** Constructor with custom reminder mode. */
9088
protected StructuredOutputCapableAgent(
9189
String name,
9290
String description,
@@ -102,23 +100,15 @@ protected StructuredOutputCapableAgent(
102100
: StructuredOutputReminder.TOOL_CHOICE;
103101
}
104102

105-
/**
106-
* Get the toolkit for tool operations.
107-
*/
103+
/** Get the toolkit for tool operations. */
108104
public Toolkit getToolkit() {
109105
return toolkit;
110106
}
111107

112-
/**
113-
* Get the memory for structured output hook.
114-
* Subclasses must implement this.
115-
*/
108+
/** Get the memory for structured output hook. Subclasses must implement this. */
116109
public abstract Memory getMemory();
117110

118-
/**
119-
* Build generate options for model calls.
120-
* Subclasses must implement this.
121-
*/
111+
/** Build generate options for model calls. Subclasses must implement this. */
122112
protected abstract GenerateOptions buildGenerateOptions();
123113

124114
// ==================== Structured Output Implementation ====================
@@ -133,9 +123,7 @@ protected final Mono<Msg> doCall(List<Msg> msgs, JsonNode outputSchema) {
133123
return executeWithStructuredOutput(msgs, null, outputSchema);
134124
}
135125

136-
/**
137-
* Execute with structured output using StructuredOutputHook.
138-
*/
126+
/** Execute with structured output using StructuredOutputHook. */
139127
private Mono<Msg> executeWithStructuredOutput(
140128
List<Msg> msgs, Class<?> targetClass, JsonNode schemaDesc) {
141129

@@ -196,9 +184,7 @@ private Mono<Msg> executeWithStructuredOutput(
196184
});
197185
}
198186

199-
/**
200-
* Create the structured output tool with validation.
201-
*/
187+
/** Create the structured output tool with validation. */
202188
private AgentTool createStructuredOutputTool(
203189
Map<String, Object> schema, Class<?> targetClass, JsonNode schemaDesc) {
204190
return new AgentTool() {
@@ -244,7 +230,7 @@ public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
244230
// Create response message
245231
Msg responseMsg =
246232
Msg.builder()
247-
.name(getName())
233+
.name(param.getAgent().getName())
248234
.role(MsgRole.ASSISTANT)
249235
.content(TextBlock.builder().text(contentText).build())
250236
.metadata(
@@ -268,9 +254,7 @@ public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
268254
};
269255
}
270256

271-
/**
272-
* Extract structured result from tool result message.
273-
*/
257+
/** Extract structured result from tool result message. */
274258
private Msg extractStructuredResult(Msg hookResultMsg) {
275259
if (hookResultMsg == null) {
276260
return null;
@@ -310,9 +294,7 @@ private Msg extractResponseData(Msg responseMsg) {
310294
return responseMsg;
311295
}
312296

313-
/**
314-
* Merge collected metadata (ChatUsage and ThinkingBlock) into the message.
315-
*/
297+
/** Merge collected metadata (ChatUsage and ThinkingBlock) into the message. */
316298
private Msg mergeCollectedMetadata(Msg msg, ChatUsage chatUsage, ThinkingBlock thinking) {
317299
// Merge ChatUsage into metadata
318300
Map<String, Object> metadata =

agentscope-examples/werewolf-hitl/src/main/java/io/agentscope/examples/werewolf/WerewolfGameConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ public class WerewolfGameConfig {
3333

3434
// Game rules
3535
public static final int MAX_ROUNDS = 30;
36-
public static final int MAX_DISCUSSION_ROUNDS = 2;
36+
public static final int MAX_DISCUSSION_ROUNDS = 1;
3737

3838
// Model configuration
39-
public static final String DEFAULT_MODEL = "qwen-plus";
39+
public static final String DEFAULT_MODEL = "qwen3-max";
4040

4141
private WerewolfGameConfig() {
4242
// Utility class

agentscope-examples/werewolf-hitl/src/main/java/io/agentscope/examples/werewolf/entity/GameState.java

Lines changed: 120 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import java.util.List;
2020
import java.util.stream.Collectors;
2121

22-
/**
23-
* Represents the current state of the Werewolf game.
24-
*/
22+
/** Represents the current state of the Werewolf game. */
2523
public class GameState {
2624
private final List<Player> allPlayers;
2725
private final List<Player> seers;
@@ -33,9 +31,17 @@ public class GameState {
3331
private Player lastPoisonedVictim;
3432
private boolean lastVictimResurrected;
3533

34+
// First night jump candidate (悍跳候选人)
35+
private Player jumpCandidate; // 第一夜狼人投票选出的悍跳候选人
36+
37+
// Sheriff election state
38+
private Player sheriff; // 当前警长
39+
private boolean speakOrderReversed; // 发言顺序是否逆序(警长决定)
40+
private boolean sheriffKilledInNight; // 警长是否在夜间被杀(用于天亮后移交警徽)
41+
3642
/**
37-
* Constructs a new GameState instance with the provided players.
38-
* Special role players (seer, witch, hunter) are detected from the list.
43+
* Constructs a new GameState instance with the provided players. Special role players (seer,
44+
* witch, hunter) are detected from the list.
3945
*
4046
* @param allPlayers the list of all players participating in the game
4147
*/
@@ -47,6 +53,11 @@ public GameState(List<Player> allPlayers) {
4753
this.seers = findPlayersByRole(Role.SEER);
4854
this.witches = findPlayersByRole(Role.WITCH);
4955
this.hunters = findPlayersByRole(Role.HUNTER);
56+
57+
// Initialize sheriff state
58+
this.sheriff = null;
59+
this.speakOrderReversed = false;
60+
this.sheriffKilledInNight = false;
5061
}
5162

5263
private List<Player> findPlayersByRole(Role role) {
@@ -184,10 +195,53 @@ public boolean isLastVictimResurrected() {
184195
return lastVictimResurrected;
185196
}
186197

198+
/**
199+
* Returns the current sheriff.
200+
*
201+
* @return the sheriff player, or null if none
202+
*/
203+
public Player getSheriff() {
204+
return sheriff;
205+
}
206+
207+
/**
208+
* Indicates whether the speaking order is reversed.
209+
*
210+
* @return true if speaking order is reversed; false otherwise
211+
*/
212+
public boolean isSpeakOrderReversed() {
213+
return speakOrderReversed;
214+
}
215+
216+
/**
217+
* Indicates whether the sheriff was killed during the night.
218+
*
219+
* @return true if sheriff was killed at night; false otherwise
220+
*/
221+
public boolean isSheriffKilledInNight() {
222+
return sheriffKilledInNight;
223+
}
224+
225+
/**
226+
* Returns the jump candidate selected by werewolves on the first night.
227+
*
228+
* @return the jump candidate player, or null if none
229+
*/
230+
public Player getJumpCandidate() {
231+
return jumpCandidate;
232+
}
233+
187234
// State modifiers
188235
/**
189-
* Increments the round counter by one to start a new round.
236+
* Sets the jump candidate selected by werewolves on the first night.
237+
*
238+
* @param jumpCandidate the werewolf player selected to jump as seer
190239
*/
240+
public void setJumpCandidate(Player jumpCandidate) {
241+
this.jumpCandidate = jumpCandidate;
242+
}
243+
244+
/** Increments the round counter by one to start a new round. */
191245
public void nextRound() {
192246
this.currentRound++;
193247
}
@@ -220,32 +274,83 @@ public void setLastVictimResurrected(boolean resurrected) {
220274
}
221275

222276
/**
223-
* Clears last night results, including the werewolf victim, poisoned victim,
224-
* and resurrection flag, preparing for the next night.
277+
* Clears last night results, including the werewolf victim, poisoned victim, and resurrection
278+
* flag, preparing for the next night.
225279
*/
226280
public void clearNightResults() {
227281
this.lastNightVictim = null;
228282
this.lastPoisonedVictim = null;
229283
this.lastVictimResurrected = false;
230284
}
231285

286+
/**
287+
* Sets the sheriff.
288+
*
289+
* @param sheriff the new sheriff player
290+
*/
291+
public void setSheriff(Player sheriff) {
292+
// Remove sheriff status from previous sheriff
293+
if (this.sheriff != null) {
294+
this.sheriff.setSheriff(false);
295+
}
296+
this.sheriff = sheriff;
297+
if (sheriff != null) {
298+
sheriff.setSheriff(true);
299+
}
300+
}
301+
302+
/**
303+
* Sets whether the speaking order is reversed.
304+
*
305+
* @param reversed true for reversed order; false for normal order
306+
*/
307+
public void setSpeakOrderReversed(boolean reversed) {
308+
this.speakOrderReversed = reversed;
309+
}
310+
311+
/**
312+
* Sets whether the sheriff was killed during the night.
313+
*
314+
* @param killed true if sheriff was killed at night; false otherwise
315+
*/
316+
public void setSheriffKilledInNight(boolean killed) {
317+
this.sheriffKilledInNight = killed;
318+
}
319+
232320
// Winning condition checks
233321
/**
234-
* Checks if werewolves meet the win condition.
235-
* Werewolves win if they are alive and their count is greater than or equal to
236-
* the number of alive villager-camp players.
322+
* Checks if werewolves meet the win condition. Werewolves win if: 1. All villagers (ordinary
323+
* villagers) are dead, OR 2. All god roles (seer, witch, hunter) are dead
237324
*
238325
* @return true if werewolves win; false otherwise
239326
*/
240327
public boolean checkWerewolvesWin() {
241328
int aliveWerewolves = getAliveWerewolves().size();
242-
int aliveVillagers = getAliveVillagers().size();
243-
return aliveWerewolves > 0 && aliveWerewolves >= aliveVillagers;
329+
if (aliveWerewolves == 0) {
330+
return false;
331+
}
332+
333+
// Check if all ordinary villagers are dead
334+
boolean allVillagersDead =
335+
getAlivePlayers().stream().noneMatch(p -> p.getRole() == Role.VILLAGER);
336+
337+
// Check if all god roles are dead
338+
boolean allGodsDead =
339+
getAlivePlayers().stream()
340+
.filter(
341+
p ->
342+
p.getRole() == Role.SEER
343+
|| p.getRole() == Role.WITCH
344+
|| p.getRole() == Role.HUNTER)
345+
.count()
346+
== 0;
347+
348+
return allVillagersDead || allGodsDead;
244349
}
245350

246351
/**
247-
* Checks if villagers meet the win condition.
248-
* Villagers win when all werewolves have been eliminated.
352+
* Checks if villagers meet the win condition. Villagers win when all werewolves have been
353+
* eliminated.
249354
*
250355
* @return true if villagers win; false otherwise
251356
*/

0 commit comments

Comments
 (0)