Line FindUnSolvedCompleteLine(List <Line> completeLines, MyPuzzle myPuzzle) { Line lineToSolve = null; // completeLines 리스트의 맨 앞에서부터 라인을 뽑아 solved인지 검사 // 맨 앞에서부터 뽑는 이유는 number clue의 숫자가 0이 아닌 라인들을 리스트 앞쪽에 몰아서 저장 해 놓았기 때문 while (completeLines.Count > 0) { lineToSolve = completeLines[0]; if (isLineSolved(lineToSolve, myPuzzle)) { lineToSolve.solved = true; completeLines.RemoveAt(0); lineToSolve = null; } else { break; } } return(lineToSolve); }
public static void Puzzle_Solve_Returns_Correct_Value_Based_On_Args_Length() { // Arrange string[] args = new[] { "1" }; var target = new MyPuzzle(2); // Act int actual = target.Solve(args); // Assert Assert.Equal(-1, actual); // Arrange args = new string[0]; target = new MyPuzzle(1); // Act actual = target.Solve(args); // Assert Assert.Equal(-1, actual); // Arrange target = new MyPuzzle(0); // Act actual = target.Solve(args); // Assert Assert.Equal(0, actual); Assert.Equal(42, target.Answer); }
public async Task Puzzle_Solve_Returns_Correct_Value_Based_On_Args_Length() { // Arrange string[] args = new[] { "1" }; var cancellationToken = CancellationToken.None; var target = new MyPuzzle(2) { Logger = Logger, }; // Act and Assert await Assert.ThrowsAsync <PuzzleException>(() => target.SolveAsync(args, cancellationToken)); // Arrange args = Array.Empty <string>(); target = new MyPuzzle(1); // Act and Assert await Assert.ThrowsAsync <PuzzleException>(() => target.SolveAsync(args, cancellationToken)); // Arrange target = new MyPuzzle(0); // Act PuzzleResult actual = await target.SolveAsync(args, cancellationToken); // Assert actual.ShouldNotBeNull(); actual.Solutions.ShouldNotBeNull(); actual.Solutions.Count.ShouldBe(1); actual.Solutions[0].ShouldBe(42); target.Answer.ShouldBe(42); }
bool isLineSolved(Line line, MyPuzzle myPuzzle) { int solids = 0; int lineCount = line.line.Count; var l = line.line; for (int i = 0; i < lineCount; ++i) { CELLSTATE currentCell = myPuzzle.puzzleState[l[i].z, l[i].y, l[i].x]; // 하나라도 BLANK인 큐브가 있으면 라인 전체가 풀리지 않은 것 // 즉, 이 라인이 풀린다면 최소 하나의 단서가 추가될 수 있음 if (currentCell == CELLSTATE.BLANK) { return(false); } if (currentCell == CELLSTATE.SOLID) { solids++; } } return(solids == line.clue.number); // SOLID가 아닌 큐브는 모두 EMPTY일 것이므로 group은 상관 없이 전체 SOLID 갯수만 보면 된다 }
List <CELLSTATE> GetBeforeLine(List <puzzleIndex> line, MyPuzzle myPuzzle) { int lineLen = line.Count; List <CELLSTATE> res = new List <CELLSTATE>(lineLen); for (int i = 0; i < lineLen; ++i) { res.Add(myPuzzle.puzzleState[line[i].z, line[i].y, line[i].x]); } return(res); }
bool isLinePassivelySolved(Line line, MyPuzzle myPuzzle) { int solids = 0; int lineCount = line.line.Count; var l = line.line; for (int i = 0; i < lineCount; ++i) { CELLSTATE currentCell = myPuzzle.puzzleState[l[i].z, l[i].y, l[i].x]; // 모든 solid를 칠하지 않았어도 empty만 맞으면 풀었다고 본다 if (currentCell == CELLSTATE.SOLID || currentCell == CELLSTATE.BLANK) { solids++; } } return(solids == line.clue.number); // SOLID 또는 BLANK가 아닌 큐브는 모두 EMPTY일 것이므로 group은 상관 없이 전체 SOLID 갯수만 보면 된다 }
public static void Puzzle_Returns_Minus_One_If_Too_Few_Arguments() { // Arrange string[] args = new[] { "1" }; Puzzle target = new MyPuzzle(2); // Act int actual = target.Solve(args); // Assert Assert.Equal(-1, actual); // Arrange args = Array.Empty <string>(); target = new MyPuzzle(1); // Act actual = target.Solve(args); // Assert Assert.Equal(-1, actual); }
bool SolveCompleteDescriptionLines(bool firstCall, int num, List <Line[, ]> linesOnFaces, List <Line> completeLines, MyPuzzle myPuzzle, LineHeap Q) { // num개의 complete-description 라인을 푼다 for (int i = 0; i < Mathf.Min(num, completeLines.Count); ++i) { // solved 아닌 라인을 찾는다 Line lineToSolve = FindUnSolvedCompleteLine(completeLines, myPuzzle); if (lineToSolve == null) { return(firstCall); // unsolved인 complete-description이 하나도 없음. first call이 아니라면 false 반환 } // line solver로 라인을 푼다 List <CELLSTATE> beforeLineSolver = GetBeforeLine(lineToSolve.line, myPuzzle); List <CELLSTATE> afterLineSolver = lineSolver.lineSolver(lineToSolve.clue, lineToSolve.line, myPuzzle); lineToSolve.mark = true; lineToSolve.solved = true; // line solver 적용 전, 후의 라인을 비교한다 int lineLen = afterLineSolver.Count; List <puzzleIndex> line = lineToSolve.line; for (int j = 0; j < lineLen; ++j) { if (beforeLineSolver[j] != afterLineSolver[j]) { // cell state 영구적으로 변경하기 myPuzzle.puzzleState[line[j].z, line[j].y, line[j].x] = afterLineSolver[j]; if (afterLineSolver[j] == CELLSTATE.EMPTY) { #if SolveTheActualPuzzle puzzleCube[line[j].z, line[j].y, line[j].x].PlayDestoryEffect(); #endif myPuzzle.myBreaks++; } else if (afterLineSolver[j] == CELLSTATE.SOLID) { #if SolveTheActualPuzzle puzzleCube[line[j].z, line[j].y, line[j].x].isProtected = true; puzzleCube[line[j].z, line[j].y, line[j].x].SetColor(Color.cyan, (int)Cube.MATERIAL_INDEX.BACKGROUND); puzzleCube[line[j].z, line[j].y, line[j].x].PlayProtectAnimation(); #endif } // 수직 라인의 우선순위 높이기 Line[] perpendicularLines = GetPerpendicularLines(linesOnFaces, lineToSolve.faceType, line[j]); if (afterLineSolver[j] == CELLSTATE.SOLID) { perpendicularLines[0].priority += 2; perpendicularLines[1].priority += 2; } else if (afterLineSolver[j] == CELLSTATE.EMPTY) { perpendicularLines[0].priority += 1; perpendicularLines[1].priority += 1; } // 수직 라인들 힙에 넣기 for (int index = 0; index < 2; ++index) { if (!perpendicularLines[index].insideHeap) { Q.Insert(perpendicularLines[index]); } } } } } return(true); }
bool SolvePuzzle(Puzzle puzzle, List <Line[, ]> linesOnFaces, List <Line> completeLines) { MyPuzzle myPuzzle = new MyPuzzle(puzzle); LineHeap Q = new LineHeap(puzzle.zLen * puzzle.yLen * puzzle.xLen); // line solver가 사용 할 우선순위 큐 // 처음 몇 개의 complete-description 라인을 마크하고 풀고, 상태가 바뀐 cell의 수직 라인들 우선순위 올리고 힙에 넣기 SolveCompleteDescriptionLines(true, 3, linesOnFaces, completeLines, myPuzzle, Q); // complete-description 라인들만 빼고 모두 힙에 넣는다 foreach (var face in linesOnFaces) { foreach (Line line in face) { if (!line.isCompleteDescription && !line.insideHeap) { Q.Insert(line); } } } // 휴리스틱 알고리즘으로 풀기... while (myPuzzle.myBreaks != puzzle.breakCount) { Line lineToSolve = Q.Pop(); if (lineToSolve == null) { // 풀다가 안 풀리면 complete-description 라인 하나씩 추가 해 주면서 진행 if (SolveCompleteDescriptionLines(false, 1, linesOnFaces, completeLines, myPuzzle, Q)) { continue; } else { Debug.Log("퍼즐 풀이 실패!"); return(false); // 퍼즐 풀이 실패! } } if (isLinePassivelySolved(lineToSolve, myPuzzle)) { lineToSolve.solved = true; // 이미 풀려있는 라인임 continue; } // line solver로 라인을 푼다 List <CELLSTATE> beforeLineSolver = GetBeforeLine(lineToSolve.line, myPuzzle); List <CELLSTATE> afterLineSolver = lineSolver.lineSolver(lineToSolve.clue, lineToSolve.line, myPuzzle); // line solver 적용 전, 후의 라인을 비교한다 int lineLen = afterLineSolver.Count; List <puzzleIndex> line = lineToSolve.line; for (int j = 0; j < lineLen; ++j) { if (beforeLineSolver[j] != afterLineSolver[j]) { lineToSolve.mark = true; // deduction을 하나라도 만들었으므로 라인을 mark한다 // cell state 영구적으로 변경하기 myPuzzle.puzzleState[line[j].z, line[j].y, line[j].x] = afterLineSolver[j]; if (afterLineSolver[j] == CELLSTATE.EMPTY) { #if SolveTheActualPuzzle puzzleCube[line[j].z, line[j].y, line[j].x].PlayDestoryEffect(); #endif myPuzzle.myBreaks++; } else if (afterLineSolver[j] == CELLSTATE.SOLID) { #if SolveTheActualPuzzle puzzleCube[line[j].z, line[j].y, line[j].x].isProtected = true; puzzleCube[line[j].z, line[j].y, line[j].x].SetColor(Color.cyan, (int)Cube.MATERIAL_INDEX.BACKGROUND); puzzleCube[line[j].z, line[j].y, line[j].x].PlayProtectAnimation(); #endif } // 수직 라인과 현재 라인의 우선순위 수정 Line[] perpendicularLines = GetPerpendicularLines(linesOnFaces, lineToSolve.faceType, line[j]); if (afterLineSolver[j] == CELLSTATE.SOLID) { lineToSolve.priority -= 2; perpendicularLines[0].priority += 2; perpendicularLines[1].priority += 2; } else if (afterLineSolver[j] == CELLSTATE.EMPTY) { lineToSolve.priority -= 1; perpendicularLines[0].priority += 1; perpendicularLines[1].priority += 1; } // 수직 라인들 힙에 넣기 for (int index = 0; index < 2; ++index) { if (!perpendicularLines[index].insideHeap) { Q.Insert(perpendicularLines[index]); } else { // 이미 힙에 있다면 float up 해 주기 Q.Floatup(perpendicularLines[index].heapIndex); } } } } // line이 풀렸는가? if (isLineSolved(lineToSolve, myPuzzle)) { lineToSolve.solved = true; } } // 성공! return(true); }