Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions docs/tutorial/06-attack-and-win.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,27 @@ You can't see the enemy base through the fog, but you don't need to. Every playe
`startPosition` is in the state, and that's where their Command Center was built:

```csharp
// Start position of the first opponent still in the game: its Command Center is there.
(int X, int Y) EnemyBase(GameState state)
// Start position of the nearest opponent still in the game: its Command Center is there.
(int X, int Y) EnemyBase(GameState state, int fromX, int fromY)
{
int me = state.You.Slot;
foreach (var p in state.Players)
{
if (p.Slot != me && !p.Eliminated)
return (p.StartPosition.X, p.StartPosition.Y);
}
return (state.MapWidth / 2, state.MapHeight / 2);
var rival = state.Players
.Where(p => p.Slot != me && !p.Eliminated)
.MinBy(p => Distance(fromX, fromY, p.StartPosition.X, p.StartPosition.Y));
return rival is null
? (state.MapWidth / 2, state.MapHeight / 2)
: (rival.StartPosition.X, rival.StartPosition.Y);
}
```

```python
def enemy_base(state):
"""Start position of the first opponent still in the game: its Command Center is there."""
def enemy_base(state, x, y):
"""Start position of the nearest opponent still in the game: its Command Center is there."""
me = state["you"]["slot"]
for p in state["players"]:
if p["slot"] != me and not p["eliminated"]:
return p["startPosition"]["x"], p["startPosition"]["y"]
rivals = [p for p in state["players"] if p["slot"] != me and not p["eliminated"]]
if rivals:
p = min(rivals, key=lambda p: distance(x, y, p["startPosition"]["x"], p["startPosition"]["y"]))
return p["startPosition"]["x"], p["startPosition"]["y"]
return state["mapWidth"] // 2, state["mapHeight"] // 2
```

Expand Down Expand Up @@ -74,7 +75,7 @@ Two kinds of `Attack` order are useful here:
else
{
// Attack-move: walk to the enemy base, fighting anything seen on the way.
var (x, y) = EnemyBase(state);
var (x, y) = EnemyBase(state, idleSoldiers[0].X, idleSoldiers[0].Y);
commands.Add(new Command("Attack", UnitIds: ids, X: x, Y: y));
}
}
Expand All @@ -97,7 +98,7 @@ Two kinds of `Attack` order are useful here:
commands.append({"type": "Attack", "unitIds": ids, "targetId": target["id"]})
else:
# Attack-move: walk to the enemy base, fighting anything seen on the way.
x, y = enemy_base(state)
x, y = enemy_base(state, idle_soldiers[0]["x"], idle_soldiers[0]["y"])
commands.append({"type": "Attack", "unitIds": ids, "x": x, "y": y})
```

Expand Down
18 changes: 9 additions & 9 deletions docs/tutorial/code/step4_attack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,16 @@ async Task<GameState> WaitForStart(string matchId)
return best;
}

// Start position of the first opponent still in the game: its Command Center is there.
(int X, int Y) EnemyBase(GameState state)
// Start position of the nearest opponent still in the game: its Command Center is there.
(int X, int Y) EnemyBase(GameState state, int fromX, int fromY)
{
int me = state.You.Slot;
foreach (var p in state.Players)
{
if (p.Slot != me && !p.Eliminated)
return (p.StartPosition.X, p.StartPosition.Y);
}
return (state.MapWidth / 2, state.MapHeight / 2);
var rival = state.Players
.Where(p => p.Slot != me && !p.Eliminated)
.MinBy(p => Distance(fromX, fromY, p.StartPosition.X, p.StartPosition.Y));
return rival is null
? (state.MapWidth / 2, state.MapHeight / 2)
: (rival.StartPosition.X, rival.StartPosition.Y);
}

// Look at this tick's state and return a list of commands to send.
Expand Down Expand Up @@ -255,7 +255,7 @@ List<Command> Decide(GameState state)
else
{
// Attack-move: walk to the enemy base, fighting anything seen on the way.
var (x, y) = EnemyBase(state);
var (x, y) = EnemyBase(state, idleSoldiers[0].X, idleSoldiers[0].Y);
commands.Add(new Command("Attack", UnitIds: ids, X: x, Y: y));
}
}
Expand Down
13 changes: 7 additions & 6 deletions docs/tutorial/code/step4_attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ def find_build_site(command_center, state):
return best


def enemy_base(state):
"""Start position of the first opponent still in the game: its Command Center is there."""
def enemy_base(state, x, y):
"""Start position of the nearest opponent still in the game: its Command Center is there."""
me = state["you"]["slot"]
for p in state["players"]:
if p["slot"] != me and not p["eliminated"]:
return p["startPosition"]["x"], p["startPosition"]["y"]
rivals = [p for p in state["players"] if p["slot"] != me and not p["eliminated"]]
if rivals:
p = min(rivals, key=lambda p: distance(x, y, p["startPosition"]["x"], p["startPosition"]["y"]))
return p["startPosition"]["x"], p["startPosition"]["y"]
return state["mapWidth"] // 2, state["mapHeight"] // 2


Expand Down Expand Up @@ -213,7 +214,7 @@ def decide(state):
commands.append({"type": "Attack", "unitIds": ids, "targetId": target["id"]})
else:
# Attack-move: walk to the enemy base, fighting anything seen on the way.
x, y = enemy_base(state)
x, y = enemy_base(state, idle_soldiers[0]["x"], idle_soldiers[0]["y"])
commands.append({"type": "Attack", "unitIds": ids, "x": x, "y": y})

return commands
Expand Down
12 changes: 10 additions & 2 deletions samples/csharp/StarterBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,16 @@ object TrainSoldier(State s, int ore)
// 5. Once we have 5 soldiers, send every idle soldier to attack the enemy's starting corner.
object Attack(State s)
{
var enemy = s.Players.FirstOrDefault(p => p.Slot != s.You.Slot && !p.Eliminated);
if (MyUnits(s, "Soldier").Count < 5 || enemy == null)
var soldiers = MyUnits(s, "Soldier");
if (soldiers.Count < 5)
{
return null;
}

var enemy = s.Players
.Where(p => p.Slot != s.You.Slot && !p.Eliminated)
.MinBy(p => Math.Max(Math.Abs(p.StartPosition.X - soldiers[0].X), Math.Abs(p.StartPosition.Y - soldiers[0].Y)));
if (enemy == null)
{
return null;
}
Expand Down
12 changes: 6 additions & 6 deletions samples/python/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,16 @@ def spend(kind):
target = min(enemies, key=lambda e: dist(sx, sy, e["x"], e["y"]))
commands.append({"type": "Attack", "unitIds": idle, "targetId": target["id"]})
else: # attack-move toward the enemy base (engages anything seen on the way)
tx, ty = self.enemy_base(s)
tx, ty = self.enemy_base(s, soldiers[0]["x"], soldiers[0]["y"])
commands.append({"type": "Attack", "unitIds": idle, "x": tx, "y": ty})
return commands

def enemy_base(self, s):
"""Start position of the first opponent still in the game."""
def enemy_base(self, s, x, y):
me = s["you"]["slot"]
for p in s["players"]:
if p["slot"] != me and not p["eliminated"]:
return p["startPosition"]["x"], p["startPosition"]["y"]
rivals = [p for p in s["players"] if p["slot"] != me and not p["eliminated"]]
if rivals:
p = min(rivals, key=lambda p: dist(x, y, p["startPosition"]["x"], p["startPosition"]["y"]))
return p["startPosition"]["x"], p["startPosition"]["y"]
return s["mapWidth"] // 2, s["mapHeight"] // 2

def find_site(self, cc, s):
Expand Down
11 changes: 6 additions & 5 deletions src/NetRts.Bots/BotView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ public BotView(BotContext context)
public bool IsOpen(int x, int y) =>
x >= 0 && y >= 0 && x < Map.Width && y < Map.Height && Map.Terrain[y][x] == '.' && !_blocked.Contains((x, y));

/// <summary>Where the opposing bases started; the first one still in the game.</summary>
public PositionDto? EnemyStart() =>
State.Players.Where(p => p.Slot != State.You.Slot && !p.Eliminated).Select(p => p.StartPosition).FirstOrDefault();
public PositionDto? EnemyStart(int fromX, int fromY) =>
State.Players.Where(p => p.Slot != State.You.Slot && !p.Eliminated)
.OrderBy(p => PlanBot.Dist(p.StartPosition.X, p.StartPosition.Y, fromX, fromY)).ThenBy(p => p.Slot)
.Select(p => p.StartPosition).FirstOrDefault();

/// <summary>Nearest visible enemy building, else the enemy's starting base.</summary>
public (int X, int Y)? AttackTarget()
Expand All @@ -59,7 +60,7 @@ public bool IsOpen(int x, int y) =>
return (building.X, building.Y);
}

return EnemyStart() is { } start ? (start.X, start.Y) : null;
return EnemyStart(cc.X, cc.Y) is { } start ? (start.X, start.Y) : null;
}

/// <summary>Where the army waits: a few tiles from the Command Center toward the map centre.</summary>
Expand Down Expand Up @@ -141,7 +142,7 @@ public bool IsOpen(int x, int y) =>
: ((int)Math.Round(ore.Average(r => r.X)), (int)Math.Round(ore.Average(r => r.Y)));
}

case SitePreference.TowardEnemy when EnemyStart() is { } enemy:
case SitePreference.TowardEnemy when EnemyStart(cc.X, cc.Y) is { } enemy:
{
var dx = Math.Sign(enemy.X - cc.X);
var dy = Math.Sign(enemy.Y - cc.Y);
Expand Down
Loading