<RuntimeDirectory>\System.Drawing.dll System.Drawing void Main() { var roomDescs = new RoomsProvider(); roomDescs.Load(@"C:\Users\Admin\Documents\isaac\levelgen\resources\rooms"); var bot1Total = new int[14]; var bot2Total = new int[14]; foreach (var fp in new DirectoryInfo(@"C:\Users\Admin\Documents\isaac\levelgen\dump2").GetFiles()) { using (var file = File.OpenRead(fp.FullName)) { Console.WriteLine(file.Name); var reader = new LayoutReader(file, roomDescs); StageBlock sb; while ((sb = reader.Read()) != null) { for (var i = 0; i < sb.Stages.Length; i++) { if (sb.Stages[i].StageId == 9 || sb.Stages[i].StageId == 12 || (sb.Stages[i].StageId == 10 && sb.Stages[i].StageType == 1) || (sb.Stages[i].StageId == 11 && sb.Stages[i].StageType == 1)) continue; var seedStr = SeedToString(sb.Seed); //Console.WriteLine("{0} (Stage {1})", SeedToString(sb.Seed), sb.Stages[i].StageId); //Console.WriteLine("--------"); Func roomToChar = (room) => { if (room == null) return "O"; if (room.StageIndex == 0 && room.RoomType == RoomType.ROOM_DEFAULT && room.RoomId == 2) return "S"; if (room.RoomType == RoomType.ROOM_BOSS) return "B"; return "X"; }; for (var y = 0; y < 13; y++) { var line = string.Join("", Enumerable.Range(0, 13).Select(k => roomToChar(sb.Stages[i].RoomGrid[y][k]))); //Console.WriteLine(line); } var bot = new Bot1(); bot.Start(sb.Stages[i]); do { } while (bot.Think()); var bot2 = new Bot2(); bot2.Start(sb.Stages[i]); do { } while (bot2.Think()); //Console.WriteLine(bot.RoomsVisited); //Console.WriteLine(bot2.RoomsVisited); bot1Total[sb.Stages[i].StageId] += bot.RoomsVisited; bot2Total[sb.Stages[i].StageId] += bot2.RoomsVisited; } } } } bot1Total.Dump(); bot2Total.Dump(); } public class RoomState { public bool Visited = false; public bool Revealed = false; public Room Room; } public class BotState { public Stage Stage; public Point Pos; public RoomState[][] GridState; public Room CurrentRoom { get { return GridState[Pos.Y][Pos.X].Room; }} public BotState(Stage stage) { Stage = stage; GridState = new RoomState[13][]; for (var j = 0; j < GridState.Length; j++) GridState[j] = new RoomState[13]; var gridStateArr = new RoomState[Stage.Rooms.Length]; for (var y = 0; y < 13; y++) { for (var x = 0; x < 13; x++) { var room = stage.RoomGrid[y][x]; if (room == null) { GridState[y][x] = new RoomState(); continue; } var state = gridStateArr[room.RoomOffset] ?? new RoomState { Room = room}; gridStateArr[room.RoomOffset] = state; GridState[y][x] = state; } } } } public abstract class Bot { public BotState State; public Room[][] Grid { get { return State.Stage.RoomGrid; } } public RoomState[][] GridState { get { return State.GridState; } } public Point GetStart() { for (var y = 0; y < 13; y++) { for (var x = 0; x < 13; x++) { var room = Grid[y][x]; if (room == null) continue; if (room.StageIndex == 0 && room.RoomType == RoomType.ROOM_DEFAULT && room.RoomId == 2 //Standard start room || ((room.StageIndex == 16 || room.StageIndex == 17) && room.RoomType == RoomType.ROOM_DEFAULT && room.RoomId == 0)) //Chest/DarkRoom start rooms { return new Point(x, y); } } } throw new Exception("Start room not found D:"); } public Point GetEnd() { for (var y = 0; y < 13; y++) { for (var x = 0; x < 13; x++) { var room = Grid[y][x]; if (room == null) continue; if (room.RoomType == RoomType.ROOM_BOSS) { return new Point(x, y); } } } throw new Exception("Boss room not found D:"); } public virtual void Start(Stage stage) { State = new BotState(stage); State.Pos = GetStart(); } public abstract bool Think(); public virtual void End() { } public static bool InBounds(Point p) { return p.X >= 0 && p.Y >= 0 && p.X < 13 && p.Y < 13; } //The points are using the top/left most grid x/y. #region shape doors public static Dictionary ShapeDoors = new Dictionary { // U R D L, U R D L {RoomShape.ROOMSHAPE_1x1, new [] { new Point(0, -1), new Point(1, 0), new Point(0, 1), new Point(-1, 0), default(Point), default(Point), default(Point), default(Point) } }, {RoomShape.ROOMSHAPE_1x2, new [] { new Point(0, -1), new Point(1, 0), new Point(0, 2), new Point(-1, 0), default(Point), new Point(1, 1), default(Point), new Point(-1, 1) } }, {RoomShape.ROOMSHAPE_2x1, new [] { new Point(0, -1), new Point(2, 0), new Point(0, 1), new Point(-1, 0), new Point(1, -1), default(Point), new Point(1, 1), default(Point) } }, {RoomShape.ROOMSHAPE_2x2, new [] { new Point(0, -1), new Point(2, 0), new Point(0, 2), new Point(-1, 0), new Point(1, -1), new Point(2, 1), new Point(1, 2), new Point(-1, 1)} }, {RoomShape.ROOMSHAPE_IH, new [] { default(Point), new Point(1, 0), default(Point), new Point(-1, 0) , default(Point), default(Point), default(Point), default(Point) } }, {RoomShape.ROOMSHAPE_IIH, new [] { default(Point), new Point(2, 0), default(Point), new Point(-1, 0) , default(Point), default(Point), default(Point), default(Point) } }, {RoomShape.ROOMSHAPE_IV, new [] { new Point(0, -1), default(Point), new Point(0, 1), default(Point), default(Point), default(Point), default(Point), default(Point) } }, {RoomShape.ROOMSHAPE_IIV, new [] { new Point(0, -1), default(Point), new Point(0, 2), default(Point), default(Point), default(Point), default(Point), default(Point) } }, {RoomShape.ROOMSHAPE_LTL, new [] { new Point(-1, 0), new Point(1, 0), new Point(-1, 2), new Point(-1, 0), new Point(0, -1), new Point(1, 1), new Point(0, 2), new Point(-2, 1) } }, {RoomShape.ROOMSHAPE_LTR, new [] { new Point(0, -1), new Point(1, 0), new Point(0, 2), new Point(-1, 0), new Point(1, 0), new Point(2, 1), new Point(1, 2), new Point(-1, 1) } }, {RoomShape.ROOMSHAPE_LBL, new [] { new Point(0, -1), new Point(2, 0), new Point(0, 1), new Point(-1, 0), new Point(1, -1), new Point(2, 1), new Point(1, 2), new Point(0,-1) } }, {RoomShape.ROOMSHAPE_LBR, new [] { new Point(0, -1), new Point(2, 0), new Point(0, 2), new Point(-1, 0), new Point(1, -1), new Point(1, 1), new Point(1, 1), new Point(-1, 1) } }, }; #endregion } public class Bot1 : Bot { Stack Visited = new Stack(); public int RoomsVisited; static RoomState[] exitRooms = new RoomState[8]; #region weights static Dictionary ShapeWeights = new Dictionary { {RoomShape.ROOMSHAPE_1x1, 1}, {RoomShape.ROOMSHAPE_1x2, 1.5f}, {RoomShape.ROOMSHAPE_2x1, 1.5f}, {RoomShape.ROOMSHAPE_2x2, 2}, {RoomShape.ROOMSHAPE_IH, 1f}, {RoomShape.ROOMSHAPE_IIH, 1.5f}, {RoomShape.ROOMSHAPE_IIV, 1.5f}, {RoomShape.ROOMSHAPE_IV, 1f}, {RoomShape.ROOMSHAPE_LBL, 1.75f}, {RoomShape.ROOMSHAPE_LBR, 1.75f}, {RoomShape.ROOMSHAPE_LTL, 1.75f}, {RoomShape.ROOMSHAPE_LTR, 1.75f}, }; #endregion void RevealRooms() { //Revealing secret rooms is fine for now since we don't actually enter them. GridState[State.Pos.Y][State.Pos.X].Revealed = true; var exits = ShapeDoors[State.CurrentRoom.Shape]; for (var i = 0; i < exits.Length; i++) { if (exits[i] == default(Point)) continue; var dstP = State.Pos.Add(exits[i]); if (!InBounds(dstP)) continue; var dstRoom = State.GridState[dstP.Y][dstP.X]; dstRoom.Revealed = true; } } bool IsPossible(Point pos, HashSet visited) { var room = State.GridState[pos.Y][pos.X]; if (!room.Revealed) return true; if (room.Revealed && room.Room == null) return false; visited.Add(pos); var exits = ShapeDoors[room.Room.Shape]; for (var i = 0; i < exits.Length; i++) { if (exits[i] == default(Point)) continue; var dstP = State.Pos.Add(exits[i]); if (!InBounds(dstP)) continue; if (visited.Contains(dstP)) continue; var dstRoom = State.GridState[dstP.Y][dstP.X]; if (dstRoom.Revealed && dstRoom.Room != null && !IsPossible(dstP, visited)) return false; } return true; } Point ChooseNextRoom() { var exits = ShapeDoors[State.CurrentRoom.Shape]; for (var i = 0; i < exits.Length; i++) { exitRooms[i] = null; if (exits[i] == default(Point)) continue; var dstP = State.Pos.Add(exits[i]); if (!InBounds(dstP)) continue; var dstRoom = State.GridState[dstP.Y][dstP.X]; if (dstRoom.Visited || dstRoom.Room == null) continue; if (dstRoom.Room.RoomType == RoomType.ROOM_BOSS) return dstP; if (dstRoom.Room.RoomType != RoomType.ROOM_DEFAULT) continue; if (!IsPossible(dstP, new HashSet())) { Func roomToChar = (room) => { if (room == null || room.Room == null) return "O"; return room.Visited ? "1" : "2"; }; for (var y = 0; y < 13; y++) { var line = string.Join("", Enumerable.Range(0, 13).Select(k => roomToChar(this.GridState[y][k]))); Console.WriteLine(line); } IsPossible(dstP, new HashSet()); continue; } exitRooms[i] = dstRoom; } Array.Sort(exitRooms, (l, r) => { if (l == null && r != null) return 1; if (r == null && l != null) return -1; if (l == null && r == null) return 0; return ShapeWeights[l.Room.Shape].CompareTo(ShapeWeights[r.Room.Shape]); }); return exitRooms[0] != null && exitRooms[0].Room != null ? new Point(exitRooms[0].Room.RoomX, exitRooms[0].Room.RoomY) : new Point(-1,-1); } public override bool Think() { GridState[State.Pos.Y][State.Pos.X].Visited = true; RevealRooms(); var nextRoomPos = ChooseNextRoom(); if (nextRoomPos.X == -1 && nextRoomPos.Y == -1) { if (Visited.Count < 1) { Func roomToChar = (room) => { if (room == null || room.Room == null) return "O"; return room.Visited ? "1" : "2"; }; for (var y = 0; y < 13; y++) { var line = string.Join("", Enumerable.Range(0, 13).Select(k => roomToChar(this.GridState[y][k]))); Console.WriteLine(line); } throw new Exception("Ran out of choices before finding the boss room"); } State.Pos = Visited.Pop(); return true; } RoomsVisited++; var nextRoom = Grid[nextRoomPos.Y][nextRoomPos.X]; var adjNextRoomPos = new Point(nextRoom.RoomX, nextRoom.RoomY); Visited.Push(State.Pos); State.Pos = adjNextRoomPos; if (State.CurrentRoom.RoomType == RoomType.ROOM_BOSS) return false; return true; } } public class Bot2 : Bot { Stack Visited = new Stack(); public int RoomsVisited; static RoomState[] exitRooms = new RoomState[8]; #region weights static Dictionary ShapeWeights = new Dictionary { {RoomShape.ROOMSHAPE_1x1, 1}, {RoomShape.ROOMSHAPE_1x2, 1.5f}, {RoomShape.ROOMSHAPE_2x1, 1.5f}, {RoomShape.ROOMSHAPE_2x2, 2}, {RoomShape.ROOMSHAPE_IH, 1f}, {RoomShape.ROOMSHAPE_IIH, 1.5f}, {RoomShape.ROOMSHAPE_IIV, 1.5f}, {RoomShape.ROOMSHAPE_IV, 1f}, {RoomShape.ROOMSHAPE_LBL, 1.75f}, {RoomShape.ROOMSHAPE_LBR, 1.75f}, {RoomShape.ROOMSHAPE_LTL, 1.75f}, {RoomShape.ROOMSHAPE_LTR, 1.75f}, }; #endregion Point ChooseNextRoom() { var exits = ShapeDoors[State.CurrentRoom.Shape]; for (var i = 0; i < exits.Length; i++) { exitRooms[i] = null; if (exits[i] == default(Point)) continue; var dstP = State.Pos.Add(exits[i]); if (!InBounds(dstP)) continue; var dstRoom = State.GridState[dstP.Y][dstP.X]; if (dstRoom.Visited || dstRoom.Room == null) continue; if (dstRoom.Room.RoomType != RoomType.ROOM_DEFAULT && dstRoom.Room.RoomType != RoomType.ROOM_BOSS) continue; exitRooms[i] = dstRoom; } Array.Sort(exitRooms, (l, r) => { if (l == null && r != null) return 1; if (r == null && l != null) return -1; if (l == null && r == null) return 0; if (l.Room == null && r.Room != null) return 1; if (r.Room == null && l.Room != null) return -1; return -ShapeWeights[l.Room.Shape].CompareTo(ShapeWeights[r.Room.Shape]); }); return exitRooms[0] != null && exitRooms[0].Room != null ? new Point(exitRooms[0].Room.RoomX, exitRooms[0].Room.RoomY) : new Point(-1, -1); } public override bool Think() { GridState[State.Pos.Y][State.Pos.X].Visited = true; var nextRoomPos = ChooseNextRoom(); if (nextRoomPos.X == -1 && nextRoomPos.Y == -1) { if (Visited.Count < 1) throw new Exception("Ran out of choices before finding the boss room"); State.Pos = Visited.Pop(); return true; } RoomsVisited++; var nextRoom = Grid[nextRoomPos.Y][nextRoomPos.X]; var adjNextRoomPos = new Point(nextRoom.RoomX, nextRoom.RoomY); Visited.Push(State.Pos); State.Pos = adjNextRoomPos; if (State.CurrentRoom.RoomType == RoomType.ROOM_BOSS) return false; return true; } } public static class PointExt { public static Point Add(this Point l, Point r) { return new Point(l.X+r.X, l.Y + r.Y); } } public class RoomDescriptor { public RoomType RoomType; public int RoomId; //variant public int RoomSubType; public RoomShape RoomShape; } public class RoomsProvider { public Dictionary, RoomDescriptor>> StageRooms; public void Load(string path) { var xmlFiles = new DirectoryInfo(path).GetFiles("*.xml"); StageRooms = new Dictionary, RoomDescriptor>>(); for (var i = 0; i < xmlFiles.Length; i++) { var stageIdx = int.Parse(Regex.Match(xmlFiles[i].Name, "^(\\d\\d)").Groups[1].Value); var doc = XDocument.Load(xmlFiles[i].FullName); var xmlRooms = doc.XPathSelectElements("//rooms/room").ToList(); var rooms = new Dictionary, RoomDescriptor>(); for (var j = 0; j < xmlRooms.Count; j++) { var id = xmlRooms[j].Get("variant"); var type = (RoomType)xmlRooms[j].Get("type"); rooms[Tuple.Create(type, id)] = new RoomDescriptor { RoomType = type, RoomId = id, RoomSubType = xmlRooms[j].Get("subtype"), RoomShape = (RoomShape)xmlRooms[j].Get("shape") }; } StageRooms[stageIdx] = rooms; } StageRooms[3] = StageRooms[1]; StageRooms[6] = StageRooms[4]; StageRooms[9] = StageRooms[7]; StageRooms[12] = StageRooms[10]; } } public class LayoutReader { Stream stream; RoomsProvider RoomsProv; public LayoutReader(Stream s, RoomsProvider roomsProv) { stream = s; RoomsProv = roomsProv; } public StageBlock Read() { if (stream.Position >= stream.Length) return null; var stages = new Stage[14]; var seed = stream.ReadUInt32(); for (var i = 0; i < 14; i++) { var stageId = stream.ReadInt8(); var stageType = stream.ReadInt8(); var roomCount = stream.ReadInt8(); var gridCount = stream.ReadInt8(); var grid = new Room[13][]; for (var j = 0; j < grid.Length; j++) grid[j] = new Room[13]; var seedStr = SeedToString(seed); var rooms = new Room[roomCount]; for (var j = 0; j < gridCount; j++) { var sRoom = Room.ReadRoom(stream, RoomsProv); var room = rooms[sRoom.RoomOffset] ?? sRoom; //Have all of the grid parts of a room point to the same reference. grid[sRoom.RoomY][sRoom.RoomX] = room; rooms[room.RoomOffset] = room; } var iAmEror = Room.ReadNonGridRoom(stream, RoomsProv); var crawlSpace = Room.ReadNonGridRoom(stream, RoomsProv); var blackMarket = Room.ReadNonGridRoom(stream, RoomsProv); Room bossRush = null; if (stageId == 6) bossRush = Room.ReadNonGridRoom(stream, RoomsProv); stages[i] = new Stage(stageId, stageType, grid, rooms); } return new StageBlock { Seed = seed, Stages = stages }; } } public class Room { public int StageIndex; public int RoomOffset; public int RoomX; public int RoomY; public RoomType RoomType; public int RoomSubType; public int RoomId; public RoomShape Shape; public static Room ReadRoom(Stream s, RoomsProvider prov) { var r = new Room { RoomOffset = s.ReadInt8(), RoomX = s.ReadInt8(), RoomY = s.ReadInt8(), StageIndex = s.ReadInt8(), RoomType = (RoomType)s.ReadInt8(), RoomSubType = s.ReadInt8(), RoomId = s.ReadInt16(), }; r.Shape = prov.StageRooms[r.StageIndex][Tuple.Create(r.RoomType, r.RoomId)].RoomShape; return r; } public static Room ReadNonGridRoom(Stream s, RoomsProvider prov) { var r = new Room { RoomOffset = -1, RoomX = -1, RoomY = -1, StageIndex = s.ReadInt8(), RoomType = (RoomType)s.ReadInt8(), RoomSubType = s.ReadInt8(), RoomId = s.ReadInt16(), }; r.Shape = prov.StageRooms[r.StageIndex][Tuple.Create(r.RoomType, r.RoomId)].RoomShape; return r; } } public enum FloorType { Special = 0, Basement = 1, Cellar = 2, BurningBasement = 3, Caves = 4, Catacombs = 5, FloodedCaves = 6, Depths = 7, Necropolis = 8, DankDepths = 9, Womb = 10, Utero = 11, ScarredWomb = 12, BlueWomb = 13, Sheol = 14, Cathedral = 15, DarkRoom = 16, Chest = 17, } public enum RoomType { ROOM_NULL = 0, ROOM_DEFAULT = 1, ROOM_SHOP = 2, ROOM_ERROR = 3, ROOM_TREASURE = 4, ROOM_BOSS = 5, ROOM_MINIBOSS = 6, ROOM_SECRET = 7, ROOM_SUPERSECRET = 8, ROOM_ARCADE = 9, ROOM_CURSE = 10, ROOM_CHALLENGE = 11, ROOM_LIBRARY = 12, ROOM_SACRIFICE = 13, ROOM_DEVIL = 14, ROOM_ANGEL = 15, ROOM_DUNGEON = 16, ROOM_BOSSRUSH = 17, ROOM_ISAACS = 18, ROOM_BARREN = 19, ROOM_CHEST = 20, ROOM_DICE = 21, ROOM_BLACK_MARKET = 22, ROOM_GREED_EXIT = 23, NUM_ROOMTYPES = 24 } public enum RoomShape { ROOMSHAPE_NULL = 0, ROOMSHAPE_1x1 = 1, ROOMSHAPE_IH = 2, ROOMSHAPE_IV = 3, ROOMSHAPE_1x2 = 4, ROOMSHAPE_IIV = 5, ROOMSHAPE_2x1 = 6, ROOMSHAPE_IIH = 7, ROOMSHAPE_2x2 = 8, ROOMSHAPE_LTL = 9, ROOMSHAPE_LTR = 10, ROOMSHAPE_LBL = 11, ROOMSHAPE_LBR = 12, NUM_ROOMSHAPES = 13 } public class Stage { public int StageId; public int StageType; public Room[][] RoomGrid; public Room[] Rooms; public Stage(int stageId, int stageType, Room[][] grid, Room[] rooms) { StageId = stageId; StageType = stageType; RoomGrid = grid; Rooms = rooms; } } public class StageBlock { public uint Seed; public Stage[] Stages; } enum Direction : int { None = 0, North = 1, East = 2, South = 4, West = 8, NorthEast = North | East, NorthWest = North | West, SouthEast = South | East, SouthWest = South | West, } // Define other methods and classes here static string SeedToString(uint num) { const string chars = "ABCDEFGHJKLMNPQRSTWXYZ01234V6789"; byte x = 0; var tnum = num; while (tnum != 0) { x += ((byte)tnum); x += (byte)(x + (x >> 7)); tnum >>= 5; } num ^= 0x0FEF7FFD; tnum = (num) << 8 | x; var ret = new char[8]; for (int i = 0; i < 6; i++) { ret[i] = chars[(int)(num >> (27 - (i * 5)) & 0x1F)]; } ret[6] = chars[(int)(tnum >> 5 & 0x1F)]; ret[7] = chars[(int)(tnum & 0x1F)]; return new string(ret); } class Rng { public uint seed; public int shift1; public int shift2; public int shift3; public uint next() { var num = seed; num ^= num >> shift1; num ^= num << shift2; num ^= num >> shift3; seed = num; return num; } };