What is the best Battleship AI?
战舰!
早在2003年(我17岁的时候),我参加了一次战舰人工智能编码比赛。尽管我输掉了那场比赛,但我很开心,从中学到了很多东西。
现在,我想重新展开这场竞赛,寻找最好的战列舰人工智能。
这里是框架,现在托管在BitBucket上。
获胜者将获得+450荣誉!比赛将于2009年11月17日开始。17日0小时后的任何输入或编辑将不被接受。(中央标准时间)尽早提交您的条目,以免错过您的机会!
为了保持这一目标,请遵循竞争精神。
游戏规则:
- 游戏的一个变种允许每次截击发射多发子弹,每艘幸存的飞船一发。
竞赛规则:
- 轮到你的时候,最多只能有一条线在运行。(不过,任何数量的线程都可能处于"挂起"状态)。
- 除了"正常"外,任何线程都不能以其他优先级运行。
- 考虑到以上两个限制,在轮到您的时候,您将得到至少3个专用CPU核心的保证。
得分:
祝你好运!玩得高兴!
编辑1:
多亏了弗里德,他在
编辑2:
由于人们对将统计数据持久化到磁盘等方面非常感兴趣,所以我添加了一些非定时设置和关闭事件,这些事件应该提供所需的功能。这是一个半突破性的变化。也就是说:接口已经修改为添加函数,但不需要主体。请下载框架的更新版本。
编辑3:
bug修复1:
错误修复2:如果一个引擎在每场比赛中超时,比赛将永远不会结束。
请下载框架的更新版本。
编辑4:
锦标赛结果:
我赞成在每场比赛中做更多的比赛。玩50个游戏就是掷硬币。我需要做1000个游戏来区分测试算法。
下载无畏1.2。
策略:
跟踪命中率大于0的船舶的所有可能位置。该列表永远不会超过~30K,因此可以精确保存,这与所有船舶(非常大)的所有可能位置列表不同。
getshot算法有两部分,一部分生成随机放炮,另一部分生成随机放炮。试图完成击沉一艘已经被击中的船。如果有一个可能的位置(从上面的列表中),所有被击中的船只都沉没了,我们会随机射击。否则,我们试图通过选择一个位置来完成击沉一艘船,这个位置消除了最可能的位置(加权)。
对于随机放炮,根据其中一艘不明船只与该位置重叠的可能性计算最佳放炮位置。
一种自适应算法,用于将舰船放置在敌方统计上不太可能射击的位置。
一种自适应算法,它喜欢在对手统计上更倾向于放置船只的位置进行射击。
放置船只时,一般不要相互接触。
这是我的条目!(最天真的解决方案)
"随机1.1"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | namespace Battleship { using System; using System.Collections.ObjectModel; using System.Drawing; public class RandomOpponent : IBattleshipOpponent { public string Name { get { return"Random"; } } public Version Version { get { return this.version; } } Random rand = new Random(); Version version = new Version(1, 1); Size gameSize; public void NewGame(Size size, TimeSpan timeSpan) { this.gameSize = size; } public void PlaceShips(ReadOnlyCollection<Ship> ships) { foreach (Ship s in ships) { s.Place( new Point( rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)), (ShipOrientation)rand.Next(2)); } } public Point GetShot() { return new Point( rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)); } public void NewMatch(string opponent) { } public void OpponentShot(Point shot) { } public void ShotHit(Point shot, bool sunk) { } public void ShotMiss(Point shot) { } public void GameWon() { } public void GameLost() { } public void MatchOver() { } } } |
这里有一个对手可以与之对抗:
- http://natekohl.net/files/farnsworthopponent.cs
我认为,与其使用固定的几何学启发的策略,不如尝试估计任何特定的未探测空间容纳船只的潜在概率。
为了做到这一点,您将探索适合当前世界视图的所有可能的船舶配置,然后根据这些配置计算概率。你可以把它想象成探索一棵树:
可能的战舰状态扩展http://natekohl.net/media/battleship-tree.png
在考虑到那棵树的所有叶子与你对世界的了解(例如,船不能重叠,所有击中的方块必须是船等)之后,你可以计算出船在每个未探测位置发生的频率,以估计船坐在那里的可能性。
这可以被视为热图,热区更可能包含船只:
每个未探测位置的概率热图http://natekohl.net/media/battleship-probs.png
我喜欢这场战舰竞赛的一点是,上面的树几乎小到可以暴力使用这种算法。如果这5艘船每艘有大约150个可能的位置,那就是1505=750亿个可能的位置。这个数字只会变小,特别是如果你能消灭整艘船。
我在上面提到的对手并没有探索整棵树;750亿到现在还很难进入。不过,它确实试图借助一些启发式方法来估计这些概率。
这不是一个成熟的答案,但似乎没有什么必要用常见的代码来混淆真正的答案。因此,我以开源的精神介绍了一些扩展/通用类。如果使用这些,请更改命名空间或尝试将所有内容编译为一个dll将不起作用。
BoardView允许您轻松使用带注释的板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.IO; namespace Battleship.ShuggyCoUk { public enum Compass { North,East,South,West } class Cell<T> { private readonly BoardView<T> view; public readonly int X; public readonly int Y; public T Data; public double Bias { get; set; } public Cell(BoardView<T> view, int x, int y) { this.view = view; this.X = x; this.Y = y; this.Bias = 1.0; } public Point Location { get { return new Point(X, Y); } } public IEnumerable<U> FoldAll<U>(U acc, Func<Cell<T>, U, U> trip) { return new[] { Compass.North, Compass.East, Compass.South, Compass.West } .Select(x => FoldLine(x, acc, trip)); } public U FoldLine<U>(Compass direction, U acc, Func<Cell<T>, U, U> trip) { var cell = this; while (true) { switch (direction) { case Compass.North: cell = cell.North; break; case Compass.East: cell = cell.East; break; case Compass.South: cell = cell.South; break; case Compass.West: cell = cell.West; break; } if (cell == null) return acc; acc = trip(cell, acc); } } public Cell<T> North { get { return view.SafeLookup(X, Y - 1); } } public Cell<T> South { get { return view.SafeLookup(X, Y + 1); } } public Cell<T> East { get { return view.SafeLookup(X+1, Y); } } public Cell<T> West { get { return view.SafeLookup(X-1, Y); } } public IEnumerable<Cell<T>> Neighbours() { if (North != null) yield return North; if (South != null) yield return South; if (East != null) yield return East; if (West != null) yield return West; } } class BoardView<T> : IEnumerable<Cell<T>> { public readonly Size Size; private readonly int Columns; private readonly int Rows; private Cell<T>[] history; public BoardView(Size size) { this.Size = size; Columns = size.Width; Rows = size.Height; this.history = new Cell<T>[Columns * Rows]; for (int y = 0; y < Rows; y++) { for (int x = 0; x < Rows; x++) history[x + y * Columns] = new Cell<T>(this, x, y); } } public T this[int x, int y] { get { return history[x + y * Columns].Data; } set { history[x + y * Columns].Data = value; } } public T this[Point p] { get { return history[SafeCalc(p.X, p.Y, true)].Data; } set { this.history[SafeCalc(p.X, p.Y, true)].Data = value; } } private int SafeCalc(int x, int y, bool throwIfIllegal) { if (x < 0 || y < 0 || x >= Columns || y >= Rows) { if (throwIfIllegal) throw new ArgumentOutOfRangeException("["+x+","+y+"]"); else return -1; } return x + y * Columns; } public void Set(T data) { foreach (var cell in this.history) cell.Data = data; } public Cell<T> SafeLookup(int x, int y) { int index = SafeCalc(x, y, false); if (index < 0) return null; return history[index]; } #region IEnumerable<Cell<T>> Members public IEnumerator<Cell<T>> GetEnumerator() { foreach (var cell in this.history) yield return cell; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public BoardView<U> Transform<U>(Func<T, U> transform) { var result = new BoardView<U>(new Size(Columns, Rows)); for (int y = 0; y < Rows; y++) { for (int x = 0; x < Columns; x++) { result[x,y] = transform(this[x, y]); } } return result; } public void WriteAsGrid(TextWriter w) { WriteAsGrid(w,"{0}"); } public void WriteAsGrid(TextWriter w, string format) { WriteAsGrid(w, x => string.Format(format, x.Data)); } public void WriteAsGrid(TextWriter w, Func<Cell<T>,string> perCell) { for (int y = 0; y < Rows; y++) { for (int x = 0; x < Columns; x++) { if (x != 0) w.Write(","); w.Write(perCell(this.SafeLookup(x, y))); } w.WriteLine(); } } #endregion } } |
一些扩展,其中一些功能在主框架中是重复的,但实际上应该由您来完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Collections.ObjectModel; namespace Battleship.ShuggyCoUk { public static class Extensions { public static bool IsIn(this Point p, Size size) { return p.X >= 0 && p.Y >= 0 && p.X < size.Width && p.Y < size.Height; } public static bool IsLegal(this Ship ship, IEnumerable<Ship> ships, Size board, Point location, ShipOrientation direction) { var temp = new Ship(ship.Length); temp.Place(location, direction); if (!temp.GetAllLocations().All(p => p.IsIn(board))) return false; return ships.Where(s => s.IsPlaced).All(s => !s.ConflictsWith(temp)); } public static bool IsTouching(this Point a, Point b) { return (a.X == b.X - 1 || a.X == b.X + 1) && (a.Y == b.Y - 1 || a.Y == b.Y + 1); } public static bool IsTouching(this Ship ship, IEnumerable<Ship> ships, Point location, ShipOrientation direction) { var temp = new Ship(ship.Length); temp.Place(location, direction); var occupied = new HashSet<Point>(ships .Where(s => s.IsPlaced) .SelectMany(s => s.GetAllLocations())); if (temp.GetAllLocations().Any(p => occupied.Any(b => b.IsTouching(p)))) return true; return false; } public static ReadOnlyCollection<Ship> MakeShips(params int[] lengths) { return new System.Collections.ObjectModel.ReadOnlyCollection<Ship>( lengths.Select(l => new Ship(l)).ToList()); } public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Rand rand) { T[] elements = source.ToArray(); // Note i > 0 to avoid final pointless iteration for (int i = elements.Length - 1; i > 0; i--) { // Swap element"i" with a random earlier element it (or itself) int swapIndex = rand.Next(i + 1); T tmp = elements[i]; elements[i] = elements[swapIndex]; elements[swapIndex] = tmp; } // Lazily yield (avoiding aliasing issues etc) foreach (T element in elements) { yield return element; } } public static T RandomOrDefault<T>(this IEnumerable<T> things, Rand rand) { int count = things.Count(); if (count == 0) return default(T); return things.ElementAt(rand.Next(count)); } } } |
我最终用了很多东西。
1 2 3 4 5 6 7 | enum OpponentsBoardState { Unknown = 0, Miss, MustBeEmpty, Hit, } |
随机化。安全但可测试,用于测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace Battleship.ShuggyCoUk { public class Rand { Random r; public Rand() { var rand = System.Security.Cryptography.RandomNumberGenerator.Create(); byte[] b = new byte[4]; rand.GetBytes(b); r = new Random(BitConverter.ToInt32(b, 0)); } public int Next(int maxValue) { return r.Next(maxValue); } public double NextDouble(double maxValue) { return r.NextDouble() * maxValue; } public T Pick<T>(IEnumerable<T> things) { return things.ElementAt(Next(things.Count())); } public T PickBias<T>(Func<T, double> bias, IEnumerable<T> things) { double d = NextDouble(things.Sum(x => bias(x))); foreach (var x in things) { if (d < bias(x)) return x; d -= bias(x); } throw new InvalidOperationException("fell off the end!"); } } } |
没有什么比这更复杂的了,这就是我的想法。它在99.9%的时间里击败随机对手。如果有人有任何其他的小挑战像这样,它是很有趣的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | namespace Battleship { using System; using System.Collections.ObjectModel; using System.Drawing; using System.Collections.Generic; using System.Linq; public class AgentSmith : IBattleshipOpponent { public string Name { get { return"Agent Smith"; } } public Version Version { get { return this.version; } } private Random rand = new Random(); private Version version = new Version(2, 1); private Size gameSize; private enum Direction { Up, Down, Left, Right } private int MissCount; private Point?[] EndPoints = new Point?[2]; private LinkedList<Point> HitShots = new LinkedList<Point>(); private LinkedList<Point> Shots = new LinkedList<Point>(); private List<Point> PatternShots = new List<Point>(); private Direction ShotDirection = Direction.Up; private void NullOutTarget() { EndPoints = new Point?[2]; MissCount = 0; } private void SetupPattern() { for (int y = 0; y < gameSize.Height; y++) for (int x = 0; x < gameSize.Width; x++) if ((x + y) % 2 == 0) PatternShots.Add(new Point(x, y)); } private bool InvalidShot(Point p) { bool InvalidShot = (Shots.Where(s => s.X == p.X && s.Y == p.Y).Any()); if (p.X < 0 | p.Y<0) InvalidShot = true; if (p.X >= gameSize.Width | p.Y >= gameSize.Height) InvalidShot = true; return InvalidShot; } private Point FireDirectedShot(Direction? direction, Point p) { ShotDirection = (Direction)direction; switch (ShotDirection) { case Direction.Up: p.Y--; break; case Direction.Down: p.Y++; break; case Direction.Left: p.X--; break; case Direction.Right: p.X++; break; } return p; } private Point FireAroundPoint(Point p) { if (!InvalidShot(FireDirectedShot(ShotDirection,p))) return FireDirectedShot(ShotDirection, p); Point testShot = FireDirectedShot(Direction.Left, p); if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Right, p); } if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Up, p); } if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Down, p); } return testShot; } private Point FireRandomShot() { Point p; do { if (PatternShots.Count > 0) PatternShots.Remove(p = PatternShots[rand.Next(PatternShots.Count)]); else do { p = FireAroundPoint(HitShots.First()); if (InvalidShot(p)) HitShots.RemoveFirst(); } while (InvalidShot(p) & HitShots.Count > 0); } while (InvalidShot(p)); return p; } private Point FireTargettedShot() { Point p; do { p = FireAroundPoint(new Point(EndPoints[1].Value.X, EndPoints[1].Value.Y)); if (InvalidShot(p) & EndPoints[1] != EndPoints[0]) EndPoints[1] = EndPoints[0]; else if (InvalidShot(p)) NullOutTarget(); } while (InvalidShot(p) & EndPoints[1] != null); if (InvalidShot(p)) p = FireRandomShot(); return p; } private void ResetVars() { Shots.Clear(); HitShots.Clear(); PatternShots.Clear(); MissCount = 0; } public void NewGame(Size size, TimeSpan timeSpan) { gameSize = size; ResetVars(); SetupPattern(); } public void PlaceShips(ReadOnlyCollection<Ship> ships) { foreach (Ship s in ships) s.Place(new Point(rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)), (ShipOrientation)rand.Next(2)); } public Point GetShot() { if (EndPoints[1] != null) Shots.AddLast(FireTargettedShot()); else Shots.AddLast(FireRandomShot()); return Shots.Last(); } public void ShotHit(Point shot, bool sunk) { HitShots.AddLast(shot); MissCount = 0; EndPoints[1] = shot; if (EndPoints[0] == null) EndPoints[0] = shot; if (sunk) NullOutTarget(); } public void ShotMiss(Point shot) { if (++MissCount == 6) NullOutTarget(); } public void GameWon() { } public void GameLost() { } public void NewMatch(string opponent) { } public void OpponentShot(Point shot) { } public void MatchOver() { } } } |
稍微压缩以占用这里的最小空间,并且仍然是可读的。
我现在没有时间来写一个完整的算法,但这里有一个想法:如果你的对手随机放置船只,放置概率不是一个简单的分布集中在(5.5,5.5)?例如,战列舰(5个单位长)在X维中的放置可能性如下:
1 2 | x 1 2 3 4 5 6 7 8 9 10 P(x) 2 4 6 8 10 10 8 6 4 2 |
同样的计算对Y也是有效的。其他的船不会有这么陡的分布,但是你最好的猜测仍然是中心。在那之后,数学方法将缓慢地从中心向外辐射对角线(可能是平均船长17/5)。前任:
1 2 3 4 5 | ........... ....x.x.... .....x..... ....x.x.... ........... |
很明显,这个想法需要增加一些随机性,但我认为纯粹从数学上来说,这是可行的方法。
关于竞争引擎的一些评论:
新游戏参数:
如果iBattleShipOpponent::NewGame用于游戏前设置并采用BoardSize,则它还应采用船只及其各自大小的列表。如果不考虑不同的船舶配置,那么允许不同的船板尺寸是没有意义的。
船舶密封:
我看不出为什么班轮被封了。除了其他基本的事情,我希望船有一个名字,所以我可以输出消息,比如("你沉没了我的0",ship.name);。我也在考虑其他扩展,所以我认为船应该是可继承的。
时间限制:
虽然1秒的时间限制对于锦标赛规则来说是有意义的,但它完全妨碍了调试。战列舰竞赛应该有一个简单的设置来忽略时间违规,以帮助开发/调试。我还建议调查system.diagnostics.process::userprocessortime/privileged processortime/totalprocessortime,以更准确地查看正在使用的时间。
沉船:
当前的API会在您击沉对手的船时通知您:
1 | ShotHit(Point shot, bool sunk); |
但不是你沉没的那艘船!我认为这是人类战舰规则的一部分,你必须宣布"你击沉了我的战舰!"(或驱逐舰、潜艇等)。
当一个人工智能试图冲出相互碰撞的船只时,这一点尤其重要。我想请求将API更改为:
1 | ShotHit(Point shot, Ship ship); |
如果船是非空的,这意味着射击是一个下沉射击,你知道你沉没的船,以及它是多久。如果射击是非下沉射击,那么船是空的,你没有进一步的信息。
CrossFire已更新。我知道它不能与法恩斯沃思或无畏竞争,但它比法恩斯沃思快得多,而且在任何人想尝试的情况下都很容易玩。这依赖于我的库的当前状态,包括在这里以便于使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.IO; using System.Collections.ObjectModel; namespace Battleship.ShuggyCoUk { public class Simple : IBattleshipOpponent { BoardView<OpponentsBoardState> opponentsBoard = new BoardView<OpponentsBoardState>(new Size(10,10)); Rand rand = new Rand(); int gridOddEven; Size size; public string Name { get { return"Simple"; } } public Version Version { get { return new Version(2, 1); }} public void NewMatch(string opponent) {} public void NewGame(System.Drawing.Size size, TimeSpan timeSpan) { this.size = size; this.opponentsBoard = new BoardView<OpponentsBoardState>(size); this.gridOddEven = rand.Pick(new[] { 0, 1 }); } public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships) { BoardView<bool> board = new BoardView<bool>(size); var AllOrientations = new[] { ShipOrientation.Horizontal, ShipOrientation.Vertical }; foreach (var ship in ships) { int avoidTouching = 3; while (!ship.IsPlaced) { var l = rand.Pick(board.Select(c => c.Location)); var o = rand.Pick(AllOrientations); if (ship.IsLegal(ships, size, l, o)) { if (ship.IsTouching(ships, l, o)&& --avoidTouching > 0) continue; ship.Place(l, o); } } } } protected virtual Point PickWhenNoTargets() { return rand.PickBias(x => x.Bias, opponentsBoard // nothing 1 in size .Where(c => (c.Location.X + c.Location.Y) % 2 == gridOddEven) .Where(c => c.Data == OpponentsBoardState.Unknown)) .Location; } private int SumLine(Cell<OpponentsBoardState> c, int acc) { if (acc >= 0) return acc; if (c.Data == OpponentsBoardState.Hit) return acc - 1; return -acc; } public System.Drawing.Point GetShot() { var targets = opponentsBoard .Where(c => c.Data == OpponentsBoardState.Hit) .SelectMany(c => c.Neighbours()) .Where(c => c.Data == OpponentsBoardState.Unknown) .ToList(); if (targets.Count > 1) { var lines = targets.Where( x => x.FoldAll(-1, SumLine).Select(r => Math.Abs(r) - 1).Max() > 1).ToList(); if (lines.Count > 0) targets = lines; } var target = targets.RandomOrDefault(rand); if (target == null) return PickWhenNoTargets(); return target.Location; } public void OpponentShot(System.Drawing.Point shot) { } public void ShotHit(Point shot, bool sunk) { opponentsBoard[shot] = OpponentsBoardState.Hit; Debug(shot, sunk); } public void ShotMiss(Point shot) { opponentsBoard[shot] = OpponentsBoardState.Miss; Debug(shot, false); } public const bool DebugEnabled = false; public void Debug(Point shot, bool sunk) { if (!DebugEnabled) return; opponentsBoard.WriteAsGrid( Console.Out, x => { string t; switch (x.Data) { case OpponentsBoardState.Unknown: return""; case OpponentsBoardState.Miss: t ="m"; break; case OpponentsBoardState.MustBeEmpty: t ="/"; break; case OpponentsBoardState.Hit: t ="x"; break; default: t ="?"; break; } if (x.Location == shot) t = t.ToUpper(); return t; }); if (sunk) Console.WriteLine("sunk!"); Console.ReadLine(); } public void GameWon() { } public void GameLost() { } public void MatchOver() { } #region Library code enum OpponentsBoardState { Unknown = 0, Miss, MustBeEmpty, Hit, } public enum Compass { North, East, South, West } class Cell<T> { private readonly BoardView<T> view; public readonly int X; public readonly int Y; public T Data; public double Bias { get; set; } public Cell(BoardView<T> view, int x, int y) { this.view = view; this.X = x; this.Y = y; this.Bias = 1.0; } public Point Location { get { return new Point(X, Y); } } public IEnumerable<U> FoldAll<U>(U acc, Func<Cell<T>, U, U> trip) { return new[] { Compass.North, Compass.East, Compass.South, Compass.West } .Select(x => FoldLine(x, acc, trip)); } public U FoldLine<U>(Compass direction, U acc, Func<Cell<T>, U, U> trip) { var cell = this; while (true) { switch (direction) { case Compass.North: cell = cell.North; break; case Compass.East: cell = cell.East; break; case Compass.South: cell = cell.South; break; case Compass.West: cell = cell.West; break; } if (cell == null) return acc; acc = trip(cell, acc); } } public Cell<T> North { get { return view.SafeLookup(X, Y - 1); } } public Cell<T> South { get { return view.SafeLookup(X, Y + 1); } } public Cell<T> East { get { return view.SafeLookup(X + 1, Y); } } public Cell<T> West { get { return view.SafeLookup(X - 1, Y); } } public IEnumerable<Cell<T>> Neighbours() { if (North != null) yield return North; if (South != null) yield return South; if (East != null) yield return East; if (West != null) yield return West; } } class BoardView<T> : IEnumerable<Cell<T>> { public readonly Size Size; private readonly int Columns; private readonly int Rows; private Cell<T>[] history; public BoardView(Size size) { this.Size = size; Columns = size.Width; Rows = size.Height; this.history = new Cell<T>[Columns * Rows]; for (int y = 0; y < Rows; y++) { for (int x = 0; x < Rows; x++) history[x + y * Columns] = new Cell<T>(this, x, y); } } public T this[int x, int y] { get { return history[x + y * Columns].Data; } set { history[x + y * Columns].Data = value; } } public T this[Point p] { get { return history[SafeCalc(p.X, p.Y, true)].Data; } set { this.history[SafeCalc(p.X, p.Y, true)].Data = value; } } private int SafeCalc(int x, int y, bool throwIfIllegal) { if (x < 0 || y < 0 || x >= Columns || y >= Rows) { if (throwIfIllegal) throw new ArgumentOutOfRangeException("[" + x +"," + y +"]"); else return -1; } return x + y * Columns; } public void Set(T data) { foreach (var cell in this.history) cell.Data = data; } public Cell<T> SafeLookup(int x, int y) { int index = SafeCalc(x, y, false); if (index < 0) return null; return history[index]; } #region IEnumerable<Cell<T>> Members public IEnumerator<Cell<T>> GetEnumerator() { foreach (var cell in this.history) yield return cell; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public BoardView<U> Transform<U>(Func<T, U> transform) { var result = new BoardView<U>(new Size(Columns, Rows)); for (int y = 0; y < Rows; y++) { for (int x = 0; x < Columns; x++) { result[x, y] = transform(this[x, y]); } } return result; } public void WriteAsGrid(TextWriter w) { WriteAsGrid(w,"{0}"); } public void WriteAsGrid(TextWriter w, string format) { WriteAsGrid(w, x => string.Format(format, x.Data)); } public void WriteAsGrid(TextWriter w, Func<Cell<T>, string> perCell) { for (int y = 0; y < Rows; y++) { for (int x = 0; x < Columns; x++) { if (x != 0) w.Write(","); w.Write(perCell(this.SafeLookup(x, y))); } w.WriteLine(); } } #endregion } public class Rand { Random r; public Rand() { var rand = System.Security.Cryptography.RandomNumberGenerator.Create(); byte[] b = new byte[4]; rand.GetBytes(b); r = new Random(BitConverter.ToInt32(b, 0)); } public int Next(int maxValue) { return r.Next(maxValue); } public double NextDouble(double maxValue) { return r.NextDouble() * maxValue; } public T Pick<T>(IEnumerable<T> things) { return things.ElementAt(Next(things.Count())); } public T PickBias<T>(Func<T, double> bias, IEnumerable<T> things) { double d = NextDouble(things.Sum(x => bias(x))); foreach (var x in things) { if (d < bias(x)) return x; d -= bias(x); } throw new InvalidOperationException("fell off the end!"); } } #endregion } public static class Extensions { public static bool IsIn(this Point p, Size size) { return p.X >= 0 && p.Y >= 0 && p.X < size.Width && p.Y < size.Height; } public static bool IsLegal(this Ship ship, IEnumerable<Ship> ships, Size board, Point location, ShipOrientation direction) { var temp = new Ship(ship.Length); temp.Place(location, direction); if (!temp.GetAllLocations().All(p => p.IsIn(board))) return false; return ships.Where(s => s.IsPlaced).All(s => !s.ConflictsWith(temp)); } public static bool IsTouching(this Point a, Point b) { return (a.X == b.X - 1 || a.X == b.X + 1) && (a.Y == b.Y - 1 || a.Y == b.Y + 1); } public static bool IsTouching(this Ship ship, IEnumerable<Ship> ships, Point location, ShipOrientation direction) { var temp = new Ship(ship.Length); temp.Place(location, direction); var occupied = new HashSet<Point>(ships .Where(s => s.IsPlaced) .SelectMany(s => s.GetAllLocations())); if (temp.GetAllLocations().Any(p => occupied.Any(b => b.IsTouching(p)))) return true; return false; } public static ReadOnlyCollection<Ship> MakeShips(params int[] lengths) { return new System.Collections.ObjectModel.ReadOnlyCollection<Ship>( lengths.Select(l => new Ship(l)).ToList()); } public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Battleship.ShuggyCoUk.Simple.Rand rand) { T[] elements = source.ToArray(); // Note i > 0 to avoid final pointless iteration for (int i = elements.Length - 1; i > 0; i--) { // Swap element"i" with a random earlier element it (or itself) int swapIndex = rand.Next(i + 1); T tmp = elements[i]; elements[i] = elements[swapIndex]; elements[swapIndex] = tmp; } // Lazily yield (avoiding aliasing issues etc) foreach (T element in elements) { yield return element; } } public static T RandomOrDefault<T>(this IEnumerable<T> things, Battleship.ShuggyCoUk.Simple.Rand rand) { int count = things.Count(); if (count == 0) return default(T); return things.ElementAt(rand.Next(count)); } } |
}
我的电脑现在正由戴尔修理,但这是我上周的工作地点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 | namespace Battleship { using System; using System.Collections.ObjectModel; using System.Drawing; using System.Collections.Generic; using System.Linq; public class BSKiller4 : OpponentExtended, IBattleshipOpponent { public string Name { get { return"BSKiller4"; } } public Version Version { get { return this.version; } } public bool showBoard = false; Random rand = new Random(); Version version = new Version(0, 4); Size gameSize; List<Point> nextShots; Queue<Point> scanShots; char[,] board; private void printBoard() { Console.WriteLine(); for (int y = 0; y < this.gameSize.Height; y++) { for (int x = 0; x < this.gameSize.Width; x++) { Console.Write(this.board[x, y]); } Console.WriteLine(); } Console.ReadKey(); } public void NewGame(Size size, TimeSpan timeSpan) { this.gameSize = size; board = new char[size.Width, size.Height]; this.nextShots = new List<Point>(); this.scanShots = new Queue<Point>(); fillScanShots(); initializeBoard(); } private void initializeBoard() { for (int y = 0; y < this.gameSize.Height; y++) { for (int x = 0; x < this.gameSize.Width; x++) { this.board[x, y] = 'O'; } } } private void fillScanShots() { int x, y; int num = gameSize.Width * gameSize.Height; for (int j = 0; j < 3; j++) { for (int i = j; i < num; i += 3) { x = i % gameSize.Width; y = i / gameSize.Height; scanShots.Enqueue(new Point(x, y)); } } } public void PlaceShips(ReadOnlyCollection<Ship> ships) { foreach (Ship s in ships) { s.Place(new Point( rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)), (ShipOrientation)rand.Next(2)); } } public Point GetShot() { if (showBoard) printBoard(); Point shot; shot = findShotRun(); if (shot.X != -1) { return shot; } if (this.nextShots.Count > 0) { shot = this.nextShots[0]; this.nextShots.RemoveAt(0); } else { shot = this.scanShots.Dequeue(); } return shot; } public void ShotHit(Point shot, bool sunk) { this.board[shot.X, shot.Y] = 'H'; if (!sunk) { addToNextShots(new Point(shot.X - 1, shot.Y)); addToNextShots(new Point(shot.X, shot.Y + 1)); addToNextShots(new Point(shot.X + 1, shot.Y)); addToNextShots(new Point(shot.X, shot.Y - 1)); } else { this.nextShots.Clear(); } } private Point findShotRun() { int run_forward_horizontal = 0; int run_backward_horizontal = 0; int run_forward_vertical = 0; int run_backward_vertical = 0; List<shotPossibilities> possible = new List<shotPossibilities>(5); // this only works if width = height for the board; for (int y = 0; y < this.gameSize.Height; y++) { for (int x = 0; x < this.gameSize.Width; x++) { // forward horiz if (this.board[x, y] == 'M') { run_forward_horizontal = 0; } else if (this.board[x, y] == 'O') { if (run_forward_horizontal >= 2) { possible.Add( new shotPossibilities( run_forward_horizontal, new Point(x, y), true)); } else { run_forward_horizontal = 0; } } else { run_forward_horizontal++; } // forward vertical if (this.board[y, x] == 'M') { run_forward_vertical = 0; } else if (this.board[y, x] == 'O') { if (run_forward_vertical >= 2) { possible.Add( new shotPossibilities( run_forward_vertical, new Point(y, x), false)); } else { run_forward_vertical = 0; } } else { run_forward_vertical++; } // backward horiz if (this.board[this.gameSize.Width - x - 1, y] == 'M') { run_backward_horizontal = 0; } else if (this.board[this.gameSize.Width - x - 1, y] == 'O') { if (run_backward_horizontal >= 2) { possible.Add( new shotPossibilities( run_backward_horizontal, new Point(this.gameSize.Width - x - 1, y), true)); } else { run_backward_horizontal = 0; } } else { run_backward_horizontal++; } // backward vertical if (this.board[y, this.gameSize.Height - x - 1] == 'M') { run_backward_vertical = 0; } else if (this.board[y, this.gameSize.Height - x - 1] == 'O') { if (run_backward_vertical >= 2) { possible.Add( new shotPossibilities( run_backward_vertical, new Point(y, this.gameSize.Height - x - 1), false)); } else { run_backward_vertical = 0; } } else { run_backward_vertical++; } } run_forward_horizontal = 0; run_backward_horizontal = 0; run_forward_vertical = 0; run_backward_vertical = 0; } Point shot; if (possible.Count > 0) { shotPossibilities shotp = possible.OrderByDescending(a => a.run).First(); //this.nextShots.Clear(); shot = shotp.shot; //if (shotp.isHorizontal) //{ // this.nextShots.RemoveAll(p => p.X != shot.X); //} //else //{ // this.nextShots.RemoveAll(p => p.Y != shot.Y); //} } else { shot = new Point(-1, -1); } return shot; } private void addToNextShots(Point p) { if (!this.nextShots.Contains(p) && p.X >= 0 && p.X < this.gameSize.Width && p.Y >= 0 && p.Y < this.gameSize.Height) { if (this.board[p.X, p.Y] == 'O') { this.nextShots.Add(p); } } } public void GameWon() { this.GameWins++; } public void NewMatch(string opponent) { System.Threading.Thread.Sleep(5); this.rand = new Random(System.Environment.TickCount); } public void OpponentShot(Point shot) { } public void ShotMiss(Point shot) { this.board[shot.X, shot.Y] = 'M'; } public void GameLost() { if (showBoard) Console.WriteLine("-----Game Over-----"); } public void MatchOver() { } } public class OpponentExtended { public int GameWins { get; set; } public int MatchWins { get; set; } public OpponentExtended() { } } public class shotPossibilities { public shotPossibilities(int r, Point s, bool h) { this.run = r; this.shot = s; this.isHorizontal = h; } public int run { get; set; } public Point shot { get; set; } public bool isHorizontal { get; set; } } } |
这是我在空闲时间所能做的最好的事情,这是不存在的。有一些游戏和比赛统计数据正在进行,因为我设置了循环的主要功能,并不断运行战列舰比赛,直到我按下一个键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | namespace Battleship { using System; using System.Collections.Generic; using System.Drawing; using System.Linq; public class BP7 : IBattleshipOpponent { public string Name { get { return"BP7"; } } public Version Version { get { return this.version; } } Random rand = new Random(); Version version = new Version(0, 7); Size gameSize; List<Point> scanShots; List<NextShot> nextShots; int wins, losses; int totalWins = 0; int totalLosses = 0; int maxWins = 0; int maxLosses = 0; int matchWins = 0; int matchLosses = 0; public enum Direction { VERTICAL = -1, UNKNOWN = 0, HORIZONTAL = 1 }; Direction hitDirection, lastShotDirection; enum ShotResult { UNKNOWN, MISS, HIT }; ShotResult[,] board; public struct NextShot { public Point point; public Direction direction; public NextShot(Point p, Direction d) { point = p; direction = d; } } public struct ScanShot { public Point point; public int openSpaces; public ScanShot(Point p, int o) { point = p; openSpaces = o; } } public void NewGame(Size size, TimeSpan timeSpan) { this.gameSize = size; scanShots = new List<Point>(); nextShots = new List<NextShot>(); fillScanShots(); hitDirection = Direction.UNKNOWN; board = new ShotResult[size.Width, size.Height]; } private void fillScanShots() { int x; for (x = 0; x < gameSize.Width - 1; x++) { scanShots.Add(new Point(x, x)); } if (gameSize.Width == 10) { for (x = 0; x < 3; x++) { scanShots.Add(new Point(9 - x, x)); scanShots.Add(new Point(x, 9 - x)); } } } public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships) { foreach (Ship s in ships) { s.Place( new Point( rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)), (ShipOrientation)rand.Next(2)); } } public Point GetShot() { Point shot; if (this.nextShots.Count > 0) { if (hitDirection != Direction.UNKNOWN) { if (hitDirection == Direction.HORIZONTAL) { this.nextShots = this.nextShots.OrderByDescending(x => x.direction).ToList(); } else { this.nextShots = this.nextShots.OrderBy(x => x.direction).ToList(); } } shot = this.nextShots.First().point; lastShotDirection = this.nextShots.First().direction; this.nextShots.RemoveAt(0); return shot; } List<ScanShot> scanShots = new List<ScanShot>(); for (int x = 0; x < gameSize.Width; x++) { for (int y = 0; y < gameSize.Height; y++) { if (board[x, y] == ShotResult.UNKNOWN) { scanShots.Add(new ScanShot(new Point(x, y), OpenSpaces(x, y))); } } } scanShots = scanShots.OrderByDescending(x => x.openSpaces).ToList(); int maxOpenSpaces = scanShots.FirstOrDefault().openSpaces; List<ScanShot> scanShots2 = new List<ScanShot>(); scanShots2 = scanShots.Where(x => x.openSpaces == maxOpenSpaces).ToList(); shot = scanShots2[rand.Next(scanShots2.Count())].point; return shot; } int OpenSpaces(int x, int y) { int ctr = 0; Point p; // spaces to the left p = new Point(x - 1, y); while (p.X >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN) { ctr++; p.X--; } // spaces to the right p = new Point(x + 1, y); while (p.X < gameSize.Width && board[p.X, p.Y] == ShotResult.UNKNOWN) { ctr++; p.X++; } // spaces to the top p = new Point(x, y - 1); while (p.Y >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN) { ctr++; p.Y--; } // spaces to the bottom p = new Point(x, y + 1); while (p.Y < gameSize.Height && board[p.X, p.Y] == ShotResult.UNKNOWN) { ctr++; p.Y++; } return ctr; } public void NewMatch(string opponenet) { wins = 0; losses = 0; } public void OpponentShot(Point shot) { } public void ShotHit(Point shot, bool sunk) { board[shot.X, shot.Y] = ShotResult.HIT; if (!sunk) { hitDirection = lastShotDirection; if (shot.X != 0) { this.nextShots.Add(new NextShot(new Point(shot.X - 1, shot.Y), Direction.HORIZONTAL)); } if (shot.Y != 0) { this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y - 1), Direction.VERTICAL)); } if (shot.X != this.gameSize.Width - 1) { this.nextShots.Add(new NextShot(new Point(shot.X + 1, shot.Y), Direction.HORIZONTAL)); } if (shot.Y != this.gameSize.Height - 1) { this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y + 1), Direction.VERTICAL)); } } else { hitDirection = Direction.UNKNOWN; this.nextShots.Clear(); // so now this works like gangbusters ?!?!?!?!?!?!?!?!? } } public void ShotMiss(Point shot) { board[shot.X, shot.Y] = ShotResult.MISS; } public void GameWon() { wins++; } public void GameLost() { losses++; } public void MatchOver() { if (wins > maxWins) { maxWins = wins; } if (losses > maxLosses) { maxLosses = losses; } totalWins += wins; totalLosses += losses; if (wins >= 51) { matchWins++; } else { matchLosses++; } } public void FinalStats() { Console.WriteLine("Games won:" + totalWins.ToString()); Console.WriteLine("Games lost:" + totalLosses.ToString()); Console.WriteLine("Game winning percentage:" + (totalWins * 1.0 / (totalWins + totalLosses)).ToString("P")); Console.WriteLine("Game losing percentage:" + (totalLosses * 1.0 / (totalWins + totalLosses)).ToString("P")); Console.WriteLine(); Console.WriteLine("Matches won:" + matchWins.ToString()); Console.WriteLine("Matches lost:" + matchLosses.ToString()); Console.WriteLine("Match winning percentage:" + (matchWins * 1.0 / (matchWins + matchLosses)).ToString("P")); Console.WriteLine("Match losing percentage:" + (matchLosses * 1.0 / (matchWins + matchLosses)).ToString("P")); Console.WriteLine("Match games won high:" + maxWins.ToString()); Console.WriteLine("Match games lost high:" + maxLosses.ToString()); Console.WriteLine(); } } } |
这个逻辑是我击败无畏者最接近的,赢得了41%的个人比赛。(事实上,它以52比49赢得了一场比赛。)奇怪的是,这门课与法恩斯沃思霍本特的比赛不如以前的那门课先进得多。
如果你是粗暴地强迫你的分析,那么你可能会发现所提供的随机支持的机制非常低效。它允许自己重新选择已经定位的位置,并允许框架强制它重复,直到它碰到一个尚未触及的位置,或者每次移动的时间限制到期。
这个对手有类似的行为(有效的位置分布是相同的),它只进行健全性检查本身,每次调用只消耗一个随机数生成(分摊)。
这将使用扩展/库应答中的类,并且我只提供关键方法/状态。
洗牌是从乔恩·斯基特的回答中提出来的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | class WellBehavedRandomOpponent : IBattleShipOpponent { Rand rand = new Rand(); List<Point> guesses; int nextGuess = 0; public void PlaceShips(IEnumerable<Ship> ships) { BoardView<bool> board = new BoardView<bool>(BoardSize); var AllOrientations = new[] { ShipOrientation.Horizontal, ShipOrientation.Vertical }; foreach (var ship in ships) { while (!ship.IsPlaced) { var l = rand.Pick(board.Select(c => c.Location)); var o = rand.Pick(AllOrientations); if (ship.IsLegal(ships, BoardSize, l, o)) ship.Place(l, o); } } } public void NewGame(Size size, TimeSpan timeSpan) { var board = new BoardView<bool>(size); this.guesses = new List<Point>( board.Select(x => x.Location).Shuffle(rand)); nextGuess = 0; } public System.Drawing.Point GetShot() { return guesses[nextGuess++]; } // empty methods left out } |
我不能参加,但如果我有时间的话,下面是我要实现的算法:
首先,当我检测到撞击时,我不会立即追击船上的其他部分——我会建立一个船的位置表,并在开始完全击沉之前,找出是否至少击打了全部五个位置。(请注意,对于多镜头变种,这是一个错误的策略-请参见注释)
继续这样的模式(应该以三个空格分隔的对角线结束),这应该击中所有4和5条长度的船,以及统计上大量的3和2条船。
开始随机打击对角线之间的点,这将抓住2和3长度的船,还没有注意到。
一旦我检测到5次撞击,我就会确定5次撞击是否在不同的船上。在两次命中位于同一水平线或垂直线上且彼此在5个位置内(可能是同一艘船上的两次命中)的位置附近再进行几次射击相对容易。如果它们是分开的船,那么继续下沉所有的船。如果发现它们是同一条船,继续上面的填充模式,直到所有5条船都就位。
该算法是一种简单的填充算法。它的主要特点是,它不会浪费时间沉船,它知道什么时候还有它不知道的船,也不会使用低效的填充模式(即,完全随机的模式将是浪费)。
最后注:
a)"中心"是板上的随机起点。这消除了该算法的主要弱点。b)虽然描述表明从一开始就立即绘制对角线,但理想情况下,算法仅在沿着这些对角线的"随机"位置拍摄。这有助于防止竞争对手对其船只受到可预测模式撞击的时间进行计时。
这描述了一个"完美"的算法,它可以让所有的船在(9x9)/2+10次射击下。
但是,可以显著改善:
一旦一艘船被撞击,在做"内部"对角线之前要确定它的大小。你可能已经找到了2号船,在这种情况下,内部对角线可以简化,以更快地找到3号船。
确定游戏的阶段并采取相应的行动。这个算法在游戏中的某一点上可能是好的,但是作为最终游戏的一部分,其他算法可能会产生更好的好处。另外,如果另一个玩家非常接近打败你,另一个算法可能会更好地工作-例如,高风险算法可能会更频繁地失败,但当它工作得很快时,你可能击败比你更接近胜利的对手。
确定竞争对手的游戏风格-这可能会给你提供他们如何计划船舶布置的线索(即,他们自己的算法很可能最快地确定他们如何布置自己的船舶-如果你唯一拥有的工具是锤子,一切看起来都像钉子)
-亚当
我的条目。
没有什么特别的,我也没有时间去补充我所有的好主意。
但它似乎玩得相当好。我们将看到它在竞争中的表现:
(将其放入文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 | using System; using System.Collections.ObjectModel; using System.Drawing; using System.Collections.Generic; using System.Linq; using System.Diagnostics; namespace Battleship { // The Empire of Japan surrendered on the deck of the USS Missouri on Sept. 2, 1945 public class USSMissouri : IBattleshipOpponent { public String Name { get { return name; } } public Version Version { get { return ver; } } #region IBattleship Interface // IBattleship::NewGame public void NewGame(Size gameSize, TimeSpan timeSpan) { size = gameSize; shotBoard = new ShotBoard(size); attackVector = new Stack<Attack>(); } // IBattleship::PlaceShips public void PlaceShips(ReadOnlyCollection<Ship> ships) { HunterBoard board; targetBoards = new List<HunterBoard>(); shotBoard = new ShotBoard(size); foreach (Ship s in ships) { board = new HunterBoard(this, size, s); targetBoards.Add(board); // REWRITE: to ensure valid board placement. s.Place( new Point( rand.Next(size.Width), rand.Next(size.Height)), (ShipOrientation)rand.Next(2)); } } // IBattleship::GetShot public Point GetShot() { Point p = new Point(); if (attackVector.Count() > 0) { p = ExtendShot(); return p; } // Contemplate a shot at every-single point, and measure how effective it would be. Board potential = new Board(size); for(p.Y=0; p.Y<size.Height; ++p.Y) { for(p.X=0; p.X<size.Width; ++p.X) { if (shotBoard.ShotAt(p)) { potential[p] = 0; continue; } foreach(HunterBoard b in targetBoards) { potential[p] += b.GetWeightAt(p); } } } // Okay, we have the shot potential of the board. // Lets pick a weighted-random spot. Point shot; shot = potential.GetWeightedRandom(rand.NextDouble()); shotBoard[shot] = Shot.Unresolved; return shot; } public Point ExtendShot() { // Lets consider North, South, East, and West of the current shot. // and measure the potential of each Attack attack = attackVector.Peek(); Board potential = new Board(size); Point[] points = attack.GetNextTargets(); foreach(Point p in points) { if (shotBoard.ShotAt(p)) { potential[p] = 0; continue; } foreach(HunterBoard b in targetBoards) { potential[p] += b.GetWeightAt(p); } } Point shot = potential.GetBestShot(); shotBoard[shot] = Shot.Unresolved; return shot; } // IBattleship::NewMatch public void NewMatch(string opponent) { } public void OpponentShot(Point shot) { } public void ShotHit(Point shot, bool sunk) { shotBoard[shot] = Shot.Hit; if (!sunk) { if (attackVector.Count == 0) // This is a first hit, open an attackVector { attackVector.Push(new Attack(this, shot)); } else { attackVector.Peek().AddHit(shot); // Add a hit to our current attack. } } // What if it is sunk? Close the top attack, which we've been pursuing. if (sunk) { if (attackVector.Count > 0) { attackVector.Pop(); } } } public void ShotMiss(Point shot) { shotBoard[shot] = Shot.Miss; foreach(HunterBoard b in targetBoards) { b.ShotMiss(shot); // Update the potential map. } } public void GameWon() { Trace.WriteLine ("I won the game!"); } public void GameLost() { Trace.WriteLine ("I lost the game!"); } public void MatchOver() { Trace.WriteLine("This match is over."); } #endregion public ShotBoard theShotBoard { get { return shotBoard; } } public Size theBoardSize { get { return size; } } private Random rand = new Random(); private Version ver = new Version(6, 3); // USS Missouri is BB-63, hence version 6.3 private String name ="USS Missouri ([email protected])"; private Size size; private List<HunterBoard> targetBoards; private ShotBoard shotBoard; private Stack<Attack> attackVector; } // An Attack is the data on the ship we are currently working on sinking. // It consists of a set of points, horizontal and vertical, from a central point. // And can be extended in any direction. public class Attack { public Attack(USSMissouri root, Point p) { Player = root; hit = p; horzExtent = new Extent(p.X, p.X); vertExtent = new Extent(p.Y, p.Y); } public Extent HorizontalExtent { get { return horzExtent; } } public Extent VerticalExtent { get { return vertExtent; } } public Point FirstHit { get { return hit; } } public void AddHit(Point p) { if (hit.X == p.X) // New hit in the vertical direction { vertExtent.Min = Math.Min(vertExtent.Min, p.Y); vertExtent.Max = Math.Max(vertExtent.Max, p.Y); } else if (hit.Y == p.Y) { horzExtent.Min = Math.Min(horzExtent.Min, p.X); horzExtent.Max = Math.Max(horzExtent.Max, p.X); } } public Point[] GetNextTargets() { List<Point> bors = new List<Point>(); Point p; p = new Point(hit.X, vertExtent.Min-1); while (p.Y >= 0 && Player.theShotBoard[p] == Shot.Hit) { if (Player.theShotBoard[p] == Shot.Miss) { break; // Don't add p to the List 'bors. } --p.Y; } if (p.Y >= 0 && Player.theShotBoard[p] == Shot.None) // Add next-target only if there is no shot here yet. { bors.Add(p); } //------------------- p = new Point(hit.X, vertExtent.Max+1); while (p.Y < Player.theBoardSize.Height && Player.theShotBoard[p] == Shot.Hit) { if (Player.theShotBoard[p] == Shot.Miss) { break; // Don't add p to the List 'bors. } ++p.Y; } if (p.Y < Player.theBoardSize.Height && Player.theShotBoard[p] == Shot.None) { bors.Add(p); } //------------------- p = new Point(horzExtent.Min-1, hit.Y); while (p.X >= 0 && Player.theShotBoard[p] == Shot.Hit) { if (Player.theShotBoard[p] == Shot.Miss) { break; // Don't add p to the List 'bors. } --p.X; } if (p.X >= 0 && Player.theShotBoard[p] == Shot.None) { bors.Add(p); } //------------------- p = new Point(horzExtent.Max+1, hit.Y); while (p.X < Player.theBoardSize.Width && Player.theShotBoard[p] == Shot.Hit) { if (Player.theShotBoard[p] == Shot.Miss) { break; // Don't add p to the List 'bors. } ++p.X; } if (p.X < Player.theBoardSize.Width && Player.theShotBoard[p] == Shot.None) { bors.Add(p); } return bors.ToArray(); } private Point hit; private Extent horzExtent; private Extent vertExtent; private USSMissouri Player; } public struct Extent { public Extent(Int32 min, Int32 max) { Min = min; Max = max; } public Int32 Min; public Int32 Max; } public class Board // The potential-Board, which measures the full potential of each square. { // A Board is the status of many things. public Board(Size boardsize) { size = boardsize; grid = new int[size.Width , size.Height]; Array.Clear(grid,0,size.Width*size.Height); } public int this[int c,int r] { get { return grid[c,r]; } set { grid[c,r] = value; } } public int this[Point p] { get { return grid[p.X, p.Y]; } set { grid[p.X, p.Y] = value; } } public Point GetWeightedRandom(double r) { Int32 sum = 0; foreach(Int32 i in grid) { sum += i; } Int32 index = (Int32)(r*sum); Int32 x=0, y=0; for(y=0; y<size.Height; ++y) { for(x=0; x<size.Width; ++x) { if (grid[x,y] == 0) continue; // Skip any zero-cells index -= grid[x,y]; if (index < 0) break; } if (index < 0) break; } if (x == 10 || y == 10) throw new Exception("WTF"); return new Point(x,y); } public Point GetBestShot() { int max=grid[0,0]; for(int y=0; y<size.Height; ++y) { for (int x=0; x<size.Width; ++x) { max = (grid[x,y] > max)? grid[x,y] : max; } } for(int y=0; y<size.Height; ++y) { for (int x=0; x<size.Width; ++x) { if (grid[x,y] == max) { return new Point(x,y); } } } return new Point(0,0); } public bool IsZero() { foreach(Int32 p in grid) { if (p > 0) { return false; } } return true; } public override String ToString() { String output =""; String horzDiv =" +----+----+----+----+----+----+----+----+----+----+ "; String disp; int x,y; output +=" A B C D E F G H I J " + horzDiv; for(y=0; y<size.Height; ++y) { output += String.Format("{0}", y+1).PadLeft(3); for(x=0; x<size.Width; ++x) { switch(grid[x,y]) { case (int)Shot.None: disp =""; break; case (int)Shot.Hit: disp ="#"; break; case (int)Shot.Miss: disp ="."; break; case (int)Shot.Unresolved: disp ="?"; break; default: disp ="!"; break; } output += String.Format("| {0}", disp.PadLeft(2)); } output +="| " + horzDiv; } return output; } protected Int32[,] grid; protected Size size; } public class HunterBoard { public HunterBoard(USSMissouri root, Size boardsize, Ship target) { size = boardsize; grid = new int[size.Width , size.Height]; Array.Clear(grid,0,size.Width*size.Height); Player = root; Target = target; Initialize(); } public void Initialize() { int x, y, i; for(y=0; y<size.Height; ++y) { for(x=0; x<size.Width - Target.Length+1; ++x) { for(i=0; i<Target.Length; ++i) { grid[x+i,y]++; } } } for(y=0; y<size.Height-Target.Length+1; ++y) { for(x=0; x<size.Width; ++x) { for(i=0; i<Target.Length; ++i) { grid[x,y+i]++; } } } } public int this[int c,int r] { get { return grid[c,r]; } set { grid[c,r] = value; } } public int this[Point p] { get { return grid[p.X, p.Y]; } set { grid[p.X, p.Y] = value; } } public void ShotMiss(Point p) { int x,y; int min, max; min = Math.Max(p.X-Target.Length+1, 0); max = Math.Min(p.X, size.Width-Target.Length); for(x=min; x<=max; ++x) { DecrementRow(p.Y, x, x+Target.Length-1); } min = Math.Max(p.Y-Target.Length+1, 0); max = Math.Min(p.Y, size.Height-Target.Length); for(y=min; y<=max; ++y) { DecrementColumn(p.X, y, y+Target.Length-1); } grid[p.X, p.Y] = 0; } public void ShotHit(Point p) { } public override String ToString() { String output = String.Format("Target size is {0} ", Target.Length); String horzDiv =" +----+----+----+----+----+----+----+----+----+----+ "; int x,y; output +=" A B C D E F G H I J " + horzDiv; for(y=0; y<size.Height; ++y) { output += String.Format("{0}", y+1).PadLeft(3); for(x=0; x<size.Width; ++x) { output += String.Format("| {0}", grid[x,y].ToString().PadLeft(2)); } output +="| " + horzDiv; } return output; } // If we shoot at point P, how does that affect the potential of the board? public Int32 GetWeightAt(Point p) { int x,y; int potential = 0; int min, max; min = Math.Max(p.X-Target.Length+1, 0); max = Math.Min(p.X, size.Width-Target.Length); for(x=min; x<=max; ++x) { if (Player.theShotBoard.isMissInRow(p.Y, x, x+Target.Length-1) == false) { ++potential; } } min = Math.Max(p.Y-Target.Length+1, 0); max = Math.Min(p.Y, size.Height-Target.Length); for(y=min; y<=max; ++y) { if (Player.theShotBoard.isMissInColumn(p.X, y, y+Target.Length-1) == false) { ++potential; } } return potential; } public void DecrementRow(int row, int rangeA, int rangeB) { int x; for(x=rangeA; x<=rangeB; ++x) { grid[x,row] = (grid[x,row]==0)? 0 : grid[x,row]-1; } } public void DecrementColumn(int col, int rangeA, int rangeB) { int y; for(y=rangeA; y<=rangeB; ++y) { grid[col,y] = (grid[col,y]==0)? 0 : grid[col,y]-1; } } private Ship Target = null; private USSMissouri Player; private Int32[,] grid; private Size size; } public enum Shot { None = 0, Hit = 1, Miss = 2, Unresolved = 3 }; public class ShotBoard { public ShotBoard(Size boardsize) { size = boardsize; grid = new Shot[size.Width , size.Height]; for(int y=0; y<size.Height; ++y) { for(int x=0; x<size.Width; ++x) { grid[x,y] = Shot.None; } } } public Shot this[int c,int r] { get { return grid[c,r]; } set { grid[c,r] = value; } } public Shot this[Point p] { get { return grid[p.X, p.Y]; } set { grid[p.X, p.Y] = value; } } public override String ToString() { String output =""; String horzDiv =" +----+----+----+----+----+----+----+----+----+----+ "; String disp; int x,y; output +=" A B C D E F G H I J " + horzDiv; for(y=0; y<size.Height; ++y) { output += String.Format("{0}", y+1).PadLeft(3); for(x=0; x<size.Width; ++x) { switch(grid[x,y]) { case Shot.None: disp =""; break; case Shot.Hit: disp ="#"; break; case Shot.Miss: disp ="."; break; case Shot.Unresolved: disp ="?"; break; default: disp ="!"; break; } output += String.Format("| {0}", disp.PadLeft(2)); } output +="| " + horzDiv; } return output; } // Functions to find shots on the board, at a specific point, or in a row or column, within a range public bool ShotAt(Point p) { return !(this[p]==Shot.None); } public bool isMissInColumn(int col, int rangeA, int rangeB) { for(int y=rangeA; y<=rangeB; ++y) { if (grid[col,y] == Shot.Miss) { return true; } } return false; } public bool isMissInRow(int row, int rangeA, int rangeB) { for(int x=rangeA; x<=rangeB; ++x) { if (grid[x,row] == Shot.Miss) { return true; } } return false; } protected Shot[,] grid; protected Size size; } } |
事实上,我认为拼图最大的问题在于它本质上是两个动作。一个动作是放置你的飞船,另一个动作是寻找敌人的飞船(不管第二部分是如何分割的,除了试图用一个随机因素打败一个时钟之外,它只是"运行你的算法")。没有机制来决定和对抗敌人的战略,这就是为什么类似的比赛围绕着连续几轮的"石头剪刀"而展开相当有趣。
另外,我认为如果你将游戏指定为一个网络协议,然后在C中提供实现该协议的框架,而不是规定所有的解决方案都应该是C,这会更酷,但这只是我的观点。
编辑:我取消了我的起点,因为我没有仔细阅读比赛规则。
我总是喜欢从中间开始,从那一点开始,在任何其他点之间留下不超过1个空格来解释那该死的子……射击之间的距离取决于沉船的位置。如果B型船是最后一艘,那么射击只需要在中间留出4个空位,以尽量减少浪费的射击。
萨里大学的詹姆斯·希瑟博士代表英国计算机协会举办了一场类似的竞赛。
对资源施加了限制——即每轮的最大处理器时间、移动之间无法存储任何状态、施加的最大堆大小。为了限制时间,人工智能可以在时段内的任何点提交移动,并在转弯结束时要求移动。
非常有趣-更多信息请访问:http://www.bcsstudentcast.com/
可能会给你更多的想法。
这不是极大极小值。其实放完船后,难道每个玩家都不能自己玩吗,结果就轮到他去击沉每一个对手的船了吗?掉头少的人获胜。
我不认为有什么好的一般策略,除了击沉被击中的船只和尽量减少射击次数,以覆盖船只可能藏匿的其他地方。
当然,对于任何非随机的事情,可能都有反策略。但我不认为有什么策略对所有可能的球员都是好的。
事实上,在Ubuntu9.10Linux中,该解决方案在MonoDevelopment中不做任何修改即可打开和运行。
这也可能,大概是有可能运行一系列的这些变化的游戏。
加入一些东西,比如一个3D飞机或者能够移动一艘飞船而不是为一个转弯而射击,可能会对游戏有很大的改变。
![概率密度][1]输入图像描述
![在此处输入图像描述][2]
我尝试比较Randon射击和一个愚蠢的狩猎/目标的结果,最后是一个复杂的搜索。
最好的解决方案似乎是创建一个概率密度函数,用于计算剩余船只使用任何单个正方形的可能性,并以具有最大值的正方形为目标。
你可以在这里看到我的结果在这里输入链接描述
我预测,成功反向工程他们的对手随机种子和调用模式的人将获胜。
但不确定这有多可能。
我不把实际的代码放进去,但我会冒一些一般性的风险:
- 因为所有飞船的大小都至少有2个单元,所以你可以使用我在太空探索v中看到的一个游戏实现的优化——它只在"寻找"目标的时候以菱形模式向备用单元开火。这消除了一半的方块,同时仍然保证你最终会找到所有的船。
- 搜索目标时的随机射击模式将在统计上在许多游戏中产生最佳结果。
你写道:
- 任何违反竞赛精神的行为都将被取消资格。
- 干涉对手是违背竞争精神的。
请定义"违背竞争精神"和"干扰对手"?
另外-为了简化,我建议您:
- 禁止在对手的CPU插槽中使用CPU。
- 不允许线程并行,而是在单个线程上提供更多的CPU时间。这将简化人工智能的编程,并且不会伤害任何CPU/内存受限的人。
PS-这是一个关于CS Post Docs的问题:这个游戏是否可以解决(即,是否有一个单一的,最好的策略?)是的,电路板的尺寸和步数使得minimax等成为强制性的,但我仍然想知道…在复杂性方面,这远非易事和下棋。
总游戏时间为1秒,具体取决于机器。一旦第二次的CPU操作将是不同的,在我的机器与锦标赛机器。如果我优化了战船算法,使其在1秒内使用最多的CPU时间,那么它将运行在可能较慢的锦标赛机器上,它将永远失败。
我不确定如何绕过这个框架的局限性,但应该加以解决。
…
一个想法是做在这个比赛中做的事情http://www.bcsstudentcast.com/
每回合的最大时间,而不是游戏的最大总时间。这样我就可以限制算法在一个已知的转弯时间内。一个游戏可能会持续50到600次以上的回合,如果我的算法管理它的总游戏时间,它可能不会给足够的时间做它最好的工作,或者它可能会给太多的时间和损失。在战列舰算法中,很难对总博弈时间进行管理。
我建议改变规则来限制回合时间,而不是总的比赛时间。
编辑
如果我写了一个算法,列出所有可能的快照,然后对它们进行排序,那么就得到最高级别的快照。生成所有可能的快照需要很长时间,所以我会让算法运行一定的时间,然后停止它。
如果有一个基于回合的限制,我可以让算法运行0.9秒,并返回最高排名的镜头,并很好地应付回合的时间限制。
如果我被限制在一秒钟的总游戏时间内,就很难确定算法在每一回合中应该运行多长时间。我想最大限度地利用我的CPU时间。如果一个游戏持续500回合,我可以把每一回合限制在0.002秒,但是如果一个游戏持续100回合,我可以给每一回合0.01秒的CPU时间。
对于一个算法来说,用半穷举搜索镜头空间来寻找当前限制下的最佳镜头是不切实际的。
游戏总时间为1秒,这限制了可有效用于在游戏中竞争的算法类型。
"战舰"是一个典型的计算机科学NP完全问题。
http://en.wikipedia.org/wiki/list_-np-complete_问题
(寻找战列舰-它在那里,在游戏和谜题下)