Skip to content

Commit 8a1288c

Browse files
authored
Merge pull request #193 from eesast/dev
mode1
2 parents afe4d74 + c742be8 commit 8a1288c

13 files changed

Lines changed: 1724 additions & 35 deletions

File tree

installer/installer/obj/Debug/net8.0/installer.AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
[assembly: System.Reflection.AssemblyCompanyAttribute("installer")]
1414
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
1515
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
16-
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7f7bcf51896d2cde320e690ca8ade74f9c03ee42")]
16+
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9f7db73aae63536d35e02c009a73a2d19a4a6e8d")]
1717
[assembly: System.Reflection.AssemblyProductAttribute("installer")]
1818
[assembly: System.Reflection.AssemblyTitleAttribute("installer")]
1919
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
2020

21-
// MSBuild WriteCodeFragment 类生成。
21+
// Generated by the MSBuild WriteCodeFragment class.
2222

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0b749b8eb167e530e80bba9ede4cd872ff8d59ae424c8f9c7bf2599a56bf71fd
1+
1b88c640623d488f80a81c10471e8c7d1e02a9c981c51eac3c8033d039a1e22f

logic/Server/GameServer.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -423,17 +423,9 @@ private void OnGameEnd()
423423
}
424424
else if (options.Mode == 1)
425425
{
426-
/*
427-
int[] s = new int[2];
428-
if (scores[1] > scores[0])
429-
s = [0, 2];
430-
else if (scores[1] == scores[0])
431-
s = [1, 1];
432-
else
433-
s = [2, 0];
434-
*/ // 得分计算方式待定
426+
bool gameCrashed = false;
427+
SendGameResult(rawMatchScores, gameCrashed);
435428
endGameSem.Release();
436-
//SendGameResult(s);
437429
}
438430
else
439431
{

logic/pve/GameLogic/board.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,15 @@ def manhattan(self, x1: int, y1: int, x2: int, y2: int) -> int:
194194
return abs(x1 - x2) + abs(y1 - y2)
195195

196196
def nearest_market(self, x: int, y: int) -> Optional[Tuple[int, int]]:
197-
"""Return (mx, my) of market with Manhattan distance ≤ 1, or None."""
197+
"""Return nearest market within Manhattan distance ≤ 1, or None."""
198+
best = None
199+
best_dist = 9999
198200
for mx, my in self.market_positions:
199-
if self.manhattan(x, y, mx, my) <= 1:
200-
return (mx, my)
201-
return None
201+
d = self.manhattan(x, y, mx, my)
202+
if d <= 1 and d < best_dist:
203+
best = (mx, my)
204+
best_dist = d
205+
return best
202206

203207
def nearest_resource(self, x: int, y: int) -> Optional[ResourcePoint]:
204208
"""Return nearest non-depleted resource within harvest range (≤ 2)."""

logic/pve/RLInterfaces/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from .base_agent import BaseAgent
1+
from .base_agent import BaseAgent, RestrictedGameEnvironment
22
from .ppo_agent import PPOAgent
33
from .training_loop import TrainingLoop, TrainingMetrics, BreakthroughEvent
44

55
__all__ = [
66
"BaseAgent",
7+
"RestrictedGameEnvironment",
78
"PPOAgent",
89
"TrainingLoop", "TrainingMetrics", "BreakthroughEvent",
910
]

logic/pve/RLInterfaces/base_agent.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,46 @@
1313
from GameLogic import GameEnvironment
1414

1515

16+
class RestrictedGameEnvironment:
17+
"""
18+
Contest-facing environment facade.
19+
20+
The official evaluator gives agents this object instead of the real
21+
GameEnvironment so submissions can only use the documented interaction
22+
methods.
23+
"""
24+
25+
__slots__ = ("__env",)
26+
27+
_ALLOWED = frozenset({"reset", "step", "action_masks"})
28+
29+
def __init__(self, env: GameEnvironment):
30+
object.__setattr__(self, "_RestrictedGameEnvironment__env", env)
31+
32+
def reset(self, *args, **kwargs):
33+
env = object.__getattribute__(self, "_RestrictedGameEnvironment__env")
34+
return env.reset(*args, **kwargs)
35+
36+
def step(self, *args, **kwargs):
37+
env = object.__getattribute__(self, "_RestrictedGameEnvironment__env")
38+
return env.step(*args, **kwargs)
39+
40+
def action_masks(self) -> np.ndarray:
41+
env = object.__getattribute__(self, "_RestrictedGameEnvironment__env")
42+
return env.action_masks()
43+
44+
def __getattribute__(self, name: str):
45+
if name.startswith("_") or name not in RestrictedGameEnvironment._ALLOWED:
46+
raise AttributeError(
47+
f"GameEnvironment attribute '{name}' is not available to agents; "
48+
"use only reset(), step(), and action_masks()."
49+
)
50+
return object.__getattribute__(self, name)
51+
52+
def __setattr__(self, name: str, value) -> None:
53+
raise AttributeError("Contest agents cannot mutate the environment facade.")
54+
55+
1656
class BaseAgent(ABC):
1757
"""
1858
Standard agent interface.

logic/pve/official_evaluator.py

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
from __future__ import annotations
2929

3030
import argparse
31+
import ast
3132
import importlib
3233
import json
33-
import math
3434
import os
3535
import sys
3636
from pathlib import Path
@@ -44,13 +44,134 @@
4444
sys.path.insert(0, str(HERE))
4545

4646
from GameLogic import GameConfig, GameEnvironment
47-
from RLInterfaces import BaseAgent
47+
from RLInterfaces import BaseAgent, RestrictedGameEnvironment
4848

4949

50-
def _smooth_score(score: float, scale: float = 1000.0) -> float:
51-
"""Bounded score smoothing using tanh followed by sigmoid."""
52-
z = math.tanh(score / scale)
53-
return 1.0 / (1.0 + math.exp(-z))
50+
_ALLOWED_ENV_ATTRS = frozenset({"reset", "step", "action_masks"})
51+
_ALLOWED_RL_IMPORTS = {
52+
"RLInterfaces",
53+
"RLInterfaces.base_agent",
54+
}
55+
56+
57+
class SubmissionRuleError(RuntimeError):
58+
"""Raised when an agent source file uses evaluator-forbidden interfaces."""
59+
60+
61+
class _AgentRuleVisitor(ast.NodeVisitor):
62+
def __init__(self):
63+
self.errors: List[str] = []
64+
self._env_aliases = [{"env"}]
65+
66+
@property
67+
def env_aliases(self) -> set:
68+
return self._env_aliases[-1]
69+
70+
def _is_env_expr(self, node: ast.AST) -> bool:
71+
if isinstance(node, ast.Name):
72+
return node.id in self.env_aliases
73+
return (
74+
isinstance(node, ast.Attribute)
75+
and node.attr == "env"
76+
and isinstance(node.value, ast.Name)
77+
and node.value.id == "self"
78+
)
79+
80+
def _error(self, node: ast.AST, message: str) -> None:
81+
self.errors.append(f"line {getattr(node, 'lineno', '?')}: {message}")
82+
83+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
84+
module = node.module or ""
85+
if module.startswith("GameLogic"):
86+
self._error(node, "imports from GameLogic are not allowed in submissions")
87+
if module.startswith("RLInterfaces") and module not in _ALLOWED_RL_IMPORTS:
88+
self._error(
89+
node,
90+
"only BaseAgent may be imported from RLInterfaces by submissions",
91+
)
92+
if module in _ALLOWED_RL_IMPORTS:
93+
for alias in node.names:
94+
if alias.name != "BaseAgent":
95+
self._error(
96+
node,
97+
"only BaseAgent may be imported from RLInterfaces by submissions",
98+
)
99+
self.generic_visit(node)
100+
101+
def visit_Import(self, node: ast.Import) -> None:
102+
for alias in node.names:
103+
if alias.name == "GameLogic" or alias.name.startswith("GameLogic."):
104+
self._error(node, "imports from GameLogic are not allowed in submissions")
105+
if alias.name == "RLInterfaces" or (
106+
alias.name.startswith("RLInterfaces.") and alias.name not in _ALLOWED_RL_IMPORTS
107+
):
108+
self._error(
109+
node,
110+
"use 'from RLInterfaces import BaseAgent' instead of importing RLInterfaces modules",
111+
)
112+
self.generic_visit(node)
113+
114+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
115+
self._env_aliases.append(set(self.env_aliases))
116+
self.generic_visit(node)
117+
self._env_aliases.pop()
118+
119+
visit_AsyncFunctionDef = visit_FunctionDef
120+
121+
def visit_Assign(self, node: ast.Assign) -> None:
122+
if self._is_env_expr(node.value):
123+
for target in node.targets:
124+
if isinstance(target, ast.Name):
125+
self.env_aliases.add(target.id)
126+
self.generic_visit(node)
127+
128+
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
129+
if node.value is not None and self._is_env_expr(node.value):
130+
if isinstance(node.target, ast.Name):
131+
self.env_aliases.add(node.target.id)
132+
self.generic_visit(node)
133+
134+
def visit_Attribute(self, node: ast.Attribute) -> None:
135+
if self._is_env_expr(node.value) and node.attr not in _ALLOWED_ENV_ATTRS:
136+
self._error(
137+
node,
138+
f"env.{node.attr} is not allowed; use only reset(), step(), action_masks(), obs, and info",
139+
)
140+
self.generic_visit(node)
141+
142+
def visit_Call(self, node: ast.Call) -> None:
143+
if (
144+
isinstance(node.func, ast.Name)
145+
and node.func.id == "getattr"
146+
and len(node.args) >= 2
147+
and self._is_env_expr(node.args[0])
148+
):
149+
attr_arg = node.args[1]
150+
if not (
151+
isinstance(attr_arg, ast.Constant)
152+
and isinstance(attr_arg.value, str)
153+
and attr_arg.value in _ALLOWED_ENV_ATTRS
154+
):
155+
self._error(
156+
node,
157+
"dynamic getattr on env is not allowed except for reset/step/action_masks",
158+
)
159+
self.generic_visit(node)
160+
161+
162+
def validate_agent_source(agent_file: Path) -> None:
163+
tree = ast.parse(agent_file.read_text(encoding="utf-8-sig"), filename=str(agent_file))
164+
visitor = _AgentRuleVisitor()
165+
visitor.visit(tree)
166+
if visitor.errors:
167+
detail = "\n ".join(visitor.errors)
168+
raise SubmissionRuleError(
169+
"Submission uses forbidden PvE interfaces. "
170+
"Agents may only interact with the environment through "
171+
"reset(), step(), action_masks(), and the returned obs/info.\n "
172+
f"{detail}"
173+
)
174+
54175

55176
def load_agent(submission_dir: str, model_path: Optional[str], env: GameEnvironment) -> BaseAgent:
56177
"""
@@ -70,6 +191,7 @@ def load_agent(submission_dir: str, model_path: Optional[str], env: GameEnvironm
70191
f"{agent_file} not found. "
71192
"Your submission must contain agent.py with class Agent(BaseAgent)."
72193
)
194+
validate_agent_source(agent_file)
73195

74196
if str(sub) not in sys.path:
75197
sys.path.insert(0, str(sub))
@@ -95,17 +217,17 @@ def load_agent(submission_dir: str, model_path: Optional[str], env: GameEnvironm
95217
"Agent class must implement load(cls, path, env) classmethod "
96218
"when a model file is provided."
97219
)
98-
return load_fn(model_path, env)
220+
return load_fn(model_path, RestrictedGameEnvironment(env))
99221

100222
# No model file → rule-based bot or agent that doesn't need weight loading
101223
# Try classmethod load with None first, then fall back to constructor
102224
load_fn = getattr(AgentClass, "load", None)
103225
if load_fn is not None:
104226
try:
105-
return load_fn(None, env)
227+
return load_fn(None, RestrictedGameEnvironment(env))
106228
except (TypeError, ValueError, NotImplementedError, FileNotFoundError):
107229
pass
108-
return AgentClass(env)
230+
return AgentClass(RestrictedGameEnvironment(env))
109231

110232

111233
def evaluate(
@@ -129,8 +251,7 @@ def evaluate(
129251
action = agent.get_action(obs)
130252
obs, _reward, terminated, truncated, info = env.step(action)
131253
ep_len += 1
132-
raw_score = info.get("score", 0.0)
133-
ep_score = _smooth_score(raw_score)
254+
ep_score = info.get("score", 0.0)
134255
done = terminated or truncated
135256

136257
scores.append(ep_score)

0 commit comments

Comments
 (0)