From 15695c6e549c1cfb7af8e895de3f566d20f92ca6 Mon Sep 17 00:00:00 2001 From: Sourav Saha <72702534+yashsaha555@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:54:25 +0530 Subject: [PATCH] Fix MiniMaxAlgorithm setScores bug and add comprehensive tests (#6566) * Fix MiniMaxAlgorithm setScores bug and add comprehensive tests - Fix bug in setScores method where scores.length % 1 == 0 always returned true - Add isPowerOfTwo helper method to properly validate array length - Add comprehensive unit tests covering edge cases and algorithm correctness - Tests include validation for power of 2 check, minimax logic, and error handling Fixes issue with incorrect validation logic in MiniMaxAlgorithm.setScores() * Fix Checkstyle violations and improve setScores validation - Replace star imports with explicit imports in test file - Remove trailing whitespaces from all lines - Add proper file ending newline - Improve setScores method to handle edge cases properly - Use direct bit manipulation for power-of-2 check as suggested - Ensure error message is printed and scores remain unchanged for invalid input All Checkstyle violations resolved and tests pass successfully. * Add missing newline at end of MiniMaxAlgorithmTest.java - Fix Checkstyle violation: NewlineAtEndOfFile - Ensure file ends with proper newline character - All tests continue to pass successfully * Fix PMD violation by utilizing isPowerOfTwo method - Replace inline bit manipulation with isPowerOfTwo method call - Resolves PMD UnusedPrivateMethod violation - Maintains same validation logic and error handling - All tests continue to pass successfully * test: improve MiniMaxAlgorithm test coverage for CodeCov - Add comprehensive test cases for isPowerOfTwo function edge cases - Test setScores method with various invalid array lengths (0, 3, 5, 6, 7, 9, 10, 15) - Add tests for large valid powers of 2 (up to 64 elements) - Ensure complete coverage of error handling branches - Increase test count from 14 to 19 tests - Fix partial coverage issues identified in CodeCov report Resolves CodeCov coverage gaps in MiniMaxAlgorithm.java * Fix checkstyle errors * Fix checkstyle errors --------- Co-authored-by: ssaha Co-authored-by: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com> --- .../others/MiniMaxAlgorithm.java | 13 +- .../others/MiniMaxAlgorithmTest.java | 277 ++++++++++++++++++ 2 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java diff --git a/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java b/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java index cd2cd02ab..e0b96570a 100644 --- a/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java +++ b/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java @@ -109,13 +109,18 @@ public class MiniMaxAlgorithm { return (n == 1) ? 0 : log2(n / 2) + 1; } + // A utility function to check if a number is a power of 2 + private boolean isPowerOfTwo(int n) { + return n > 0 && (n & (n - 1)) == 0; + } + public void setScores(int[] scores) { - if (scores.length % 1 == 0) { - this.scores = scores; - height = log2(this.scores.length); - } else { + if (!isPowerOfTwo(scores.length)) { System.out.println("The number of scores must be a power of 2."); + return; } + this.scores = scores; + height = log2(this.scores.length); } public int[] getScores() { diff --git a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java new file mode 100644 index 000000000..821eb3f16 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java @@ -0,0 +1,277 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test class for MiniMaxAlgorithm + * Tests the minimax algorithm implementation for game tree evaluation + */ +class MiniMaxAlgorithmTest { + + private MiniMaxAlgorithm miniMax; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + void setUp() { + miniMax = new MiniMaxAlgorithm(); + System.setOut(new PrintStream(outputStream)); + } + + @Test + void testConstructorCreatesValidScores() { + // The default constructor should create scores array of length 8 (2^3) + assertEquals(8, miniMax.getScores().length); + assertEquals(3, miniMax.getHeight()); + + // All scores should be positive (between 1 and 99) + for (int score : miniMax.getScores()) { + assertTrue(score >= 1 && score <= 99); + } + } + + @Test + void testSetScoresWithValidPowerOfTwo() { + int[] validScores = {10, 20, 30, 40}; + miniMax.setScores(validScores); + + assertArrayEquals(validScores, miniMax.getScores()); + assertEquals(2, miniMax.getHeight()); // log2(4) = 2 + } + + @Test + void testSetScoresWithInvalidLength() { + int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 + miniMax.setScores(invalidScores); + + // Should print error message and not change the scores + String output = outputStream.toString(); + assertTrue(output.contains("The number of scores must be a power of 2.")); + + // Scores should remain unchanged (original length 8) + assertEquals(8, miniMax.getScores().length); + } + + @Test + void testSetScoresWithZeroLength() { + int[] emptyScores = {}; // Length 0 is not a power of 2 + miniMax.setScores(emptyScores); + + // Should print error message and not change the scores + String output = outputStream.toString(); + assertTrue(output.contains("The number of scores must be a power of 2.")); + + // Scores should remain unchanged (original length 8) + assertEquals(8, miniMax.getScores().length); + } + + @Test + void testSetScoresWithVariousInvalidLengths() { + // Test multiple invalid lengths to ensure isPowerOfTwo function is fully covered + int[][] invalidScoreArrays = { + {1, 2, 3, 4, 5}, // Length 5 + {1, 2, 3, 4, 5, 6}, // Length 6 + {1, 2, 3, 4, 5, 6, 7}, // Length 7 + new int[9], // Length 9 + new int[10], // Length 10 + new int[15] // Length 15 + }; + + for (int[] invalidScores : invalidScoreArrays) { + // Clear the output stream for each test + outputStream.reset(); + miniMax.setScores(invalidScores); + + // Should print error message for each invalid length + String output = outputStream.toString(); + assertTrue(output.contains("The number of scores must be a power of 2."), "Failed for array length: " + invalidScores.length); + } + + // Scores should remain unchanged (original length 8) + assertEquals(8, miniMax.getScores().length); + } + + @Test + void testSetScoresWithSingleElement() { + int[] singleScore = {42}; + miniMax.setScores(singleScore); + + assertArrayEquals(singleScore, miniMax.getScores()); + assertEquals(0, miniMax.getHeight()); // log2(1) = 0 + } + + @Test + void testMiniMaxWithKnownScores() { + // Test with a known game tree: [3, 12, 8, 2] + int[] testScores = {3, 12, 8, 2}; + miniMax.setScores(testScores); + + // Maximizer starts: should choose max(min(3,12), min(8,2)) = max(3, 2) = 3 + int result = miniMax.miniMax(0, true, 0, false); + assertEquals(3, result); + } + + @Test + void testMiniMaxWithMinimizerFirst() { + // Test with minimizer starting first + int[] testScores = {3, 12, 8, 2}; + miniMax.setScores(testScores); + + // Minimizer starts: should choose min(max(3,12), max(8,2)) = min(12, 8) = 8 + int result = miniMax.miniMax(0, false, 0, false); + assertEquals(8, result); + } + + @Test + void testMiniMaxWithLargerTree() { + // Test with 8 elements: [5, 6, 7, 4, 5, 3, 6, 2] + int[] testScores = {5, 6, 7, 4, 5, 3, 6, 2}; + miniMax.setScores(testScores); + + // Maximizer starts + int result = miniMax.miniMax(0, true, 0, false); + // Expected: max(min(max(5,6), max(7,4)), min(max(5,3), max(6,2))) + // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6 + assertEquals(6, result); + } + + @Test + void testMiniMaxVerboseOutput() { + int[] testScores = {3, 12, 8, 2}; + miniMax.setScores(testScores); + + miniMax.miniMax(0, true, 0, true); + + String output = outputStream.toString(); + assertTrue(output.contains("Maximizer")); + assertTrue(output.contains("Minimizer")); + assertTrue(output.contains("chooses")); + } + + @Test + void testGetRandomScoresLength() { + int[] randomScores = MiniMaxAlgorithm.getRandomScores(4, 50); + assertEquals(16, randomScores.length); // 2^4 = 16 + + // All scores should be between 1 and 50 + for (int score : randomScores) { + assertTrue(score >= 1 && score <= 50); + } + } + + @Test + void testGetRandomScoresWithDifferentParameters() { + int[] randomScores = MiniMaxAlgorithm.getRandomScores(2, 10); + assertEquals(4, randomScores.length); // 2^2 = 4 + + // All scores should be between 1 and 10 + for (int score : randomScores) { + assertTrue(score >= 1 && score <= 10); + } + } + + @Test + void testMainMethod() { + // Test that main method runs without errors + assertDoesNotThrow(() -> MiniMaxAlgorithm.main(new String[] {})); + + String output = outputStream.toString(); + assertTrue(output.contains("The best score for")); + assertTrue(output.contains("Maximizer")); + } + + @Test + void testHeightCalculation() { + // Test height calculation for different array sizes + int[] scores2 = {1, 2}; + miniMax.setScores(scores2); + assertEquals(1, miniMax.getHeight()); // log2(2) = 1 + + int[] scores16 = new int[16]; + miniMax.setScores(scores16); + assertEquals(4, miniMax.getHeight()); // log2(16) = 4 + } + + @Test + void testEdgeCaseWithZeroScores() { + int[] zeroScores = {0, 0, 0, 0}; + miniMax.setScores(zeroScores); + + int result = miniMax.miniMax(0, true, 0, false); + assertEquals(0, result); + } + + @Test + void testEdgeCaseWithNegativeScores() { + int[] negativeScores = {-5, -2, -8, -1}; + miniMax.setScores(negativeScores); + + // Tree evaluation with maximizer first: + // Level 1 (minimizer): min(-5,-2) = -5, min(-8,-1) = -8 + // Level 0 (maximizer): max(-5, -8) = -5 + int result = miniMax.miniMax(0, true, 0, false); + assertEquals(-5, result); + } + + void tearDown() { + System.setOut(originalOut); + } + + @Test + void testSetScoresWithNegativeLength() { + // This test ensures the first condition of isPowerOfTwo (n > 0) is tested + // Although we can't directly create an array with negative length, + // we can test edge cases around zero and ensure proper validation + + // Test with array length 0 (edge case for n > 0 condition) + int[] emptyArray = new int[0]; + outputStream.reset(); + miniMax.setScores(emptyArray); + + String output = outputStream.toString(); + assertTrue(output.contains("The number of scores must be a power of 2.")); + assertEquals(8, miniMax.getScores().length); // Should remain unchanged + } + + @Test + void testSetScoresWithLargePowerOfTwo() { + // Test with a large power of 2 to ensure the algorithm works correctly + int[] largeValidScores = new int[32]; // 32 = 2^5 + for (int i = 0; i < largeValidScores.length; i++) { + largeValidScores[i] = i + 1; + } + + miniMax.setScores(largeValidScores); + assertArrayEquals(largeValidScores, miniMax.getScores()); + assertEquals(5, miniMax.getHeight()); // log2(32) = 5 + } + + @Test + void testSetScoresValidEdgeCases() { + // Test valid powers of 2 to ensure isPowerOfTwo returns true correctly + int[][] validPowersOf2 = { + new int[1], // 1 = 2^0 + new int[2], // 2 = 2^1 + new int[4], // 4 = 2^2 + new int[8], // 8 = 2^3 + new int[16], // 16 = 2^4 + new int[64] // 64 = 2^6 + }; + + int[] expectedHeights = {0, 1, 2, 3, 4, 6}; + + for (int i = 0; i < validPowersOf2.length; i++) { + miniMax.setScores(validPowersOf2[i]); + assertEquals(validPowersOf2[i].length, miniMax.getScores().length, "Failed for array length: " + validPowersOf2[i].length); + assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length); + } + } +}