Skip to content
Snippets Groups Projects
Commit 6a25efc0 authored by Ghala Almasri's avatar Ghala Almasri
Browse files

Added Maven Project Folder

parent b6adea32
No related branches found
No related tags found
No related merge requests found
Showing
with 614 additions and 0 deletions
File added
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>MagicSquareGame</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- JUnit 5 API and Jupiter Engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Surefire Plugin to run tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
</plugin>
</plugins>
</build>
</project>
File added
File added
File added
File added
// Importing the Random class for shuffling the magic square
import java.util.Random;
public class MagicSquare {
private int size;
// Using a 2-D array as advised
private int[][] square;
// Class to store the square matrix (as advised)
public MagicSquare(int size) {
this.size = size;
this.square = new int[size][size];
generateMagicSquare();
}
// Generating a magic square using the given algorithm
// Adjusting where necessary (e.g - 1-based indexing to 0-based)
private void generateMagicSquare() {
int x = 0, y = size / 2;
for (int i = 1; i <= size * size; i++) {
square[x][y] = i;
int newX = (x - 1 + size) % size;
int newY = (y - 1 + size) % size;
if (square[newX][newY] == 0) {
x = newX;
y = newY;
} else {
x = (x + 1) % size;
}
}
}
public void shuffleSquare() {
Random rand = new Random();
for (int i = 0; i < size * size; i++) { // Repeat for n^2 (size^2) times
int row = rand.nextInt(size); // Choosing a random elt
int col = rand.nextInt(size); // Choosing a random elt
// Swapping w/ random neighbor
// U, D, L, R corresponds to 0,1,2,3
int direction = rand.nextInt(4); // Swapping w/ random neighbor
// Shuffling the magic square simulates what the player does, just with randomized values
// This is why we call swapElement() in shuffleSquare() with true (swap happens during shuffling)
swapElement(row, col, direction, true);
}
}
// Swaps elts in the magic square to shuffle them
// Does row/column shifts in the even that the swap moves the elt outside the square matrix (wrap around)
public boolean swapElement(int row, int col, int direction, boolean isShuffling) {
if (direction == 0) { // Up move
if (row == 0) { // First row → shift column up
int firstVal = square[0][col];
for (int i = 0; i < size - 1; i++) {
square[i][col] = square[i + 1][col];
}
square[size - 1][col] = firstVal;
} else { // Just swap up with adjacent element
int temp = square[row][col];
square[row][col] = square[row - 1][col];
square[row - 1][col] = temp;
}
} else if (direction == 1) { // Down move
if (row == size - 1) { // Last row → shift column down
int lastVal = square[size - 1][col];
for (int i = size - 1; i > 0; i--) {
square[i][col] = square[i - 1][col];
}
square[0][col] = lastVal;
} else { // Just swap down with adjacent element
int temp = square[row][col];
square[row][col] = square[row + 1][col];
square[row + 1][col] = temp;
}
} else if (direction == 2) { // Left move
if (col == 0) { // First column → shift entire row left to wrap around
int firstVal = square[row][0];
for (int j = 0; j < size - 1; j++) {
square[row][j] = square[row][j + 1];
}
square[row][size - 1] = firstVal;
} else { // Just swap left with adjacent element
int temp = square[row][col];
square[row][col] = square[row][col - 1];
square[row][col - 1] = temp;
}
} else if (direction == 3) { // Right move
if (col == size - 1) { // Last column → perform a row shift
int lastVal = square[row][size - 1];
for (int j = size - 1; j > 0; j--) {
square[row][j] = square[row][j - 1];
}
square[row][0] = lastVal;
} else { // Just swap right with adjacent element
int temp = square[row][col];
square[row][col] = square[row][col + 1];
square[row][col + 1] = temp;
}
} else {
return false; // Invalid direction
}
return true; // Successful swap
}
public boolean isMagicSquare() {
int magicSum = size * (size * size + 1) / 2;
for (int i = 0; i < size; i++) {
// Reset rowSum and colSum at the start of each iteration
int rowSum = 0, colSum = 0;
for (int j = 0; j < size; j++) {
rowSum += square[i][j];
// Switch i and j since Java uses row-major order
colSum += square[j][i];
}
if (rowSum != magicSum || colSum != magicSum) return false;
}
// Need a separate for loop to iterate over the diagonals
int diag1 = 0, diag2 = 0;
for (int i = 0; i < size; i++) {
diag1 += square[i][i];
diag2 += square[i][size - 1 - i];
}
// This will return true if and only if both diagonals match the magic sum
return diag1 == magicSum && diag2 == magicSum;
}
public void display() {
// Iterate over each row
for (int[] row : square) {
// Iterate over each number in the current row (since row is an array, an iterable)
for (int num : row) {
// Print 3 spaces for the number including digits and padding
// This makes the grid look even
System.out.printf("%3d ", num);
}
// After printing a row, move to the next line
System.out.println();
}
}
}
\ No newline at end of file
File added
// To play SFX, must first import Java sound lib
// Doing a wildcard import to avoid importing each class one-by-one
import javax.sound.sampled.*;
// To check and open audio files and handle file errors
import java.io.File;
import java.io.IOException;
// For input
import java.util.Scanner;
public class MagicSquareGame {
// No need to recreated
private static final String PURPLE = "\u001B[35m";
private static final String RESET = "\u001B[0m";
public static void printTitle() {
//String PURPLE = "\u001B[35m"; // ANSI escape code for purple
// String RESET = "\u001B[0m"; // Reset color
// Store ASCII art as an array
String[] asciiArt = { // Store ASCII art as an array of lines
"█ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ ",
"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █",
"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █",
"█ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ",
"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █",
"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █",
"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █"
};
// Set the color of the text to purple
System.out.println(PURPLE); // Set text color to purple
// Iterate over each line in the ASCII art array print it with a typing effect
for (String line : asciiArt) {
typewriterEffect(line);
}
// Reset color after printing
System.out.println(RESET);
}
// The purpose of this function is to make the game seem more dynamic
public static void typewriterEffect(String text) {
// Setting a fixed delay of 10ms to achieve typing effect
// (Storing in var to make code more readable)
int delay = 10;
for (int i = 0; i < text.length(); i++) {
System.out.print(text.charAt(i));
try {
//
Thread.sleep(delay);
// Highly unlikely that the current sleeping thread will be interrupted
// But the Thread class in Java requires error handling
} catch (InterruptedException e) {
// Good practice to invoke interrupt() so the thread doesn't lose the info that was interrupted
Thread.currentThread().interrupt();
}
}
System.out.println();
}
public static void playSound(String soundFile) {
try {
File file = new File(soundFile);
if (!file.exists()) return; // Fails silently if file is missing
// AudioInputStream represents an audio file that is being read as a stream of bytes
AudioInputStream audioStream = AudioSystem.getAudioInputStream(file);
// Creating an audio player for playback
Clip clip = AudioSystem.getClip();
// Loafing audio into clip
clip.open(audioStream);
// Playing audio
clip.start();
// Halt other threads until sound finishes playing
// Need to divide by 1000 since Thread.sleep takes milliseconds not microseconds (1000 microseconds fit into one millisecond)
Thread.sleep(clip.getMicrosecondLength() / 1000);
} catch (Exception ignored) {
// Fault masking -- Silently fail
// Sound wasn't a requirement to begin with, so the player doesn't need to know that there's smth w/ sound file
}
}
public static void clearScreen() {
// Don't need exception handling b/c clearing the screen wasn't a req in the first place
// And it's highly unlikely for the ANSI escape seq to fail (except w/ Windows CMD/ some IDEs like Eclipse)
System.out.print("\033[H\033[2J");
// Flushing the buffer to clear the screen immediately (ensure there are no glitches)
System.out.flush();
}
public static void main(String[] args) {
// Reading input from terminal
Scanner scanner = new Scanner(System.in);
// Flag to control game loop
boolean playAgain = true;
while (playAgain) {
clearScreen();
printTitle();
typewriterEffect("Welcome to the Magic Square Game!");
typewriterEffect("Your goal is to reconstruct the magic square.");
System.out.println("\n========== PART A: MAGIC SQUARE GENERATION ==========");
int size;
while (true) {
System.out.print("Enter an odd integer for the magic square size: ");
// If the 's input is not an integer, display an error message and consume the invalid input
if (!scanner.hasNextInt()) {
System.out.println("Invalid input. Please enter a positive odd integer.");
scanner.next();
// Skip the rest of the loop, go back to the start of the loop
continue;
}
// Read integer input and check whether it's both pos&&odd, in which case you can exit the loop
size = scanner.nextInt();
if (size % 2 == 1 && size > 0) break;
System.out.println("Invalid input. Please enter a positive odd integer.");
}
// Instantiate a new MagicSquare object and display the magic square
// This is what lets us use the logic in MagicSquare.java
MagicSquare magicSquare = new MagicSquare(size);
System.out.println("\nGenerated Magic Square:");
magicSquare.display();
System.out.print("\nPress any key to continue to Part B...");
// First one consumes the leftover newline from input
scanner.nextLine();
// Second one waits for input before moving to Part B
scanner.nextLine();
clearScreen();
System.out.println("\n========== PART B: MAGIC SQUARE GAME ==========\n");
magicSquare.display();
System.out.println("\nShuffling the Magic Square...\n");
try { Thread.sleep(300); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
playSound("shuffle.wav");
magicSquare.shuffleSquare();
System.out.println("\nShuffled Magic Square:\n");
magicSquare.display();
System.out.println("\nTo win, simply reconstruct the original magic square!");
int moves = 0;
int moveLimit = 20;
// Loop runs until player wins/ runs out of moves
while (!magicSquare.isMagicSquare() && moves < moveLimit) {
System.out.println("\nMoves Left: " + (moveLimit - moves));
System.out.print("Enter move (row col direction [U/D/L/R] or X to exit):");
// Initializing variables as unset
boolean validInput = false;
int row = -1, col = -1, direction = -1;
while (!validInput) {
// Read the entire line and trim extra spaces
String inputLine = scanner.nextLine().trim(); // Read full line, remove extra spaces
// Deal w/ player wanting to exit first
if (inputLine.equalsIgnoreCase("X")) {
System.out.println("\nThanks for playing! Goodbye!");
return;
}
// Split input into parts (using whitespace regex)
String[] parts = inputLine.split("\\s+");
// The user's input must consist of 3 parts (row, col, dir), otherwise invalid
if (parts.length != 3) {
System.out.println("Invalid input. Please enter row, column, and direction (or X to exit).");
continue;
}
// To validate row input, first check if it's even a number then ensure it's non-negative
if (!parts[0].matches("-?\\d+")) {
System.out.println("Invalid row. Must be a number.");
continue;
}
row = Integer.parseInt(parts[0]) - 1;
if (row < 0 || row >= size) {
System.out.println("Invalid row. Must be between 1 and " + size);
continue;
}
// Use same logic for column input validation
if (!parts[1].matches("-?\\d+")) {
System.out.println("Invalid column. Must be a number.");
continue;
}
col = Integer.parseInt(parts[1]) - 1;
if (col < 0 || col >= size) {
System.out.println("Invalid column. Must be between 1 and " + size);
continue;
}
// 🔹 Check if Direction is valid (U, D, L, R)
char dir;
if (parts[2].length() == 1) {
// use charAt(0) since toUpperCase() returns a string
dir = parts[2].toUpperCase().charAt(0);
} else {
System.out.println("Invalid direction. Use U, D, L, or R.");
continue;
}
switch (dir) {
case 'U': direction = 0; break;
case 'D': direction = 1; break;
case 'L': direction = 2; break;
case 'R': direction = 3; break;
default: direction = -1;
}
if (direction == -1) {
System.out.println("Invalid direction. Use U, D, L, or R.");
continue;
}
// If we reach this point, then the input must be valid
validInput = true;
}
// Important that we call swapElement with false (swap does NOT happen during shuffling)
if (magicSquare.swapElement(row, col, direction, false)) {
moves++;
System.out.println();
magicSquare.display();
}
}
// Check whether the player
if (magicSquare.isMagicSquare()) {
// Reporting the number of moves as required
System.out.println("\nYou solved the magic square in " + moves + " moves!");
playSound("victory.wav");
} else {
System.out.println("\nGame Over! You ran out of moves.");
playSound("defeat.wav");
}
// Giving player option to replay and validating their response
String response;
while (true) {
System.out.print("\nWould you like to play again? (Y/N): ");
response = scanner.next().trim().toUpperCase();
if (response.equalsIgnoreCase("Y")){
playAgain = true;
break;
} else if (response.equalsIgnoreCase("N")){
playAgain = false;
break;
} else{
System.out.println("Invalid input. Please enter either 'Y' for yes or 'N' for no.");
}
}
}
System.out.println("\nThanks for playing! Goodbye!");
scanner.close();
}
}
\ No newline at end of file
File added
@echo off
javac MagicSquare2.java MagicSquareGame2.java
java MagicSquareGame2
\ No newline at end of file
#!/bin/bash
javac MagicSquare.java MagicSquareGame.java
java MagicSquareGame
\ No newline at end of file
File added
File added
File added
File added
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MagicSquareGameTest{
// Normal case: No error thrown if sound file exists
@Test
void PlaySoundNormal () {
assertDoesNotThrow(() -> {
MagicSquareGame.playSound("shuffling.wav");
});
}
// Abnormal case: Even if sound file does not exist, fail silently
@Test
void PlaySoundAbnormal () {
assertDoesNotThrow(() -> {
MagicSquareGame.playSound("misc.wav");
});
}
// Normal case: Screen is cleared with no issues (still need to check console output)
@Test
void ClearScreenNormal () {
assertDoesNotThrow (() -> {
MagicSquareGame.clearScreen();
});
}
}
\ No newline at end of file
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MagicSquareTest{
// Normal case: 5x5 square should be generated with no issues
@Test
void GenerateMagicSquareNormal() {
MagicSquare magicSquare = new MagicSquare(5);
assertNotNull(magicSquare);
}
// Abnormal case: -5x-5 square is not possible since dimensions must be non-negative
@Test
void GenerateMagicSquareAbnormal() {
assertThrows(NegativeArraySizeException.class, () -> {
new MagicSquare(-5);
});
}
// Boundary case: A 1x1 square should be generated with no isses
@Test
void GenerateMagicSquareBoundary() {
MagicSquare magicSquare = new MagicSquare(1);
assertNotNull(magicSquare);
}
// Normal case: Swap is valid (within bounds, but no wrap around)
@Test
void SwapElementNormal() {
MagicSquare magicSquare = new MagicSquare(3);
assertTrue(magicSquare.swapElement(1, 1, 3, false));
}
// Abnormal case: Swap is invalid because the direction is invalid
@Test
void SwapElementAbnormal() {
MagicSquare magicSquare = new MagicSquare(3);
assertFalse(magicSquare.swapElement(2, 2, 5, false));
}
// Boundary case: Swap is valid (wrap around - up)
@Test
void SwapElementBoundaryUp() {
MagicSquare magicSquare = new MagicSquare(3);
assertTrue(magicSquare.swapElement(0, 1, 0, false));
}
// Boundary case: Swap is valid (wrap around - down)
@Test
void SwapElementBoundaryDown() {
MagicSquare magicSquare = new MagicSquare(3);
assertTrue(magicSquare.swapElement(2, 2, 1, false));
}
// Boundary case: Swap is valid (wrap around - left)
@Test
void SwapElementBoundaryLeft() {
MagicSquare magicSquare = new MagicSquare(3);
assertTrue(magicSquare.swapElement(1, 0, 2, false));
}
// Boundary case: Swap is valid (wrap around - right)
@Test
void SwapElementBoundaryRight() {
MagicSquare magicSquare = new MagicSquare(3);
assertTrue(magicSquare.swapElement(1, 2, 3, false));
}
// Normal case: A freshly generated magic square should indeed be magic
@Test
void IsMagicSquareNormal() {
MagicSquare magicSquare = new MagicSquare(3);
assertTrue(magicSquare.isMagicSquare());
}
// Abnormal case: A magic square w/ one element swapped out of place should no longer be magic
@Test
void IsMagicSquareAbnormal() {
MagicSquare magicSquare = new MagicSquare(3);
magicSquare.swapElement(1, 0, 3, false);
assertFalse(magicSquare.isMagicSquare());
}
// Boundary case: A 1x1 square is trivially magic
@Test
void IsMagicSquareBoundary() {
MagicSquare magicSquare = new MagicSquare(1);
assertTrue(magicSquare.isMagicSquare());
}
// Normal case: shuffling the magic square results in a square which is no longer magic
@Test
void ShuffleSquareNormal() {
MagicSquare magicSquare = new MagicSquare(3);
magicSquare.shuffleSquare();
assertFalse(magicSquare.isMagicSquare());
}
}
\ No newline at end of file
File added
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment