tashrique-ahmed

CoinStrip Game Implementation

CoinStrip Game Implementation

Overview

The CoinStrip Game simulates a two-player board game where players take turns moving coins leftward under specific rules. The game is implemented in Java using the structure5.Vector<E> class and follows the TwoPlayerGame interface.

Code Review and Analysis

1. Game Initialization

Code Section:

public CoinStrip(int numTiles, int numCoins) {
    numberOfTiles = numTiles;
    currentPlayer = 1;
    tiles = new Vector<>(numberOfTiles);
    for (int m = 0; m < numberOfTiles; m++) {
        tiles.add(-1);
    }
    generateBoard();
    while (coinCount() < 3) {
        generateBoard();
    }
}

Strengths:

  • The constructor ensures the board is initialized with the required number of tiles.
  • The generateBoard() method randomly places coins, ensuring variation across games.
  • The while loop ensures at least 3 coins are present, satisfying game rules.

Opportunities for Improvement:

Optimization: The generateBoard() method might run multiple times before reaching a valid state. Instead, generate coins while creating the board to avoid redundant iterations.

Code Comments: Add comments to explain the initialization logic for better readability.

2. Board Representation

Code Section:

tiles = new Vector<>(numberOfTiles);
for (int m = 0; m < numberOfTiles; m++) {
    tiles.add(-1);
}

Strengths:

  • The use of Vector<Integer> from the structure5 library allows dynamic resizing and easy manipulation of the board.
  • The -1 placeholder makes it clear that a tile is unoccupied.

Considerations for Engineers:

  • This design prioritizes simplicity but requires frequent iterations (e.g., checking occupied tiles). A sparse representation (e.g., Map<Integer, Integer> where key = position, value = coin) might be more efficient.

3. Move Validation

Code Section:

public boolean isValidMove(int resource, int updatedValue) {
    if (!tiles.contains(resource)) {
        System.out.println("\nThere is no such coin as [" + resource + "]. Try again!");
        return false;
    }
    if (updatedValue < 1 || updatedValue > numberOfTiles) {
        System.out.println("\nYou can't move this coin [" + updatedValue + "] steps. Try again!");
        return false;
    }
    for (int i = 1; i <= updatedValue; i++) {
        if (tiles.get(getResource(resource) - i) > -1) {
            System.out.println("\nYou can't move this coin. Try again!");
            return false;
        }
    }
    return true;
}

Strengths:

  • Thorough validation of moves, including checks for out-of-bounds and illegal placements.
  • Prevents moves that violate the game's rules, such as jumping over other coins or occupying the same tile.

Opportunities for Improvement:

Error Messages: Consolidate error messages for better clarity. Currently, multiple checks may confuse the user.

Code Efficiency: The loop for checking tiles (for (int i = 1; i <= updatedValue; i++)) could be replaced with a single range check.

Example Optimization:

if (tiles.subList(getResource(resource) - updatedValue, getResource(resource)).contains(resource)) {
    return false;
}

4. Displaying the Board

public void displayBoard() {
    for (int i = 0; i < numberOfTiles; i++) {
        if (tiles.get(i) > 0) {
            System.out.print(tiles.get(i) + " ");
        } else {
            System.out.print("- ");
        }
    }
}

Strengths:

  • Clear and minimalistic visualization of the board.
  • Helps players easily identify coin positions and empty tiles.

Suggestions:

Add column indices for better player guidance, especially on larger boards.

Use a newline at the end to avoid cluttered output.

Enhanced Example:

public void displayBoard() {
    System.out.print("Index: ");
    for (int i = 0; i < numberOfTiles; i++) {
        System.out.print(i + " ");
    }
    System.out.println();
    System.out.print("Tiles: ");
    for (int i = 0; i < numberOfTiles; i++) {
        System.out.print((tiles.get(i) > 0 ? tiles.get(i) : "-") + " ");
    }
    System.out.println("\n");
}

5. Turn Management

public void takeATurn() {
    Scanner sc = new Scanner(System.in);
    System.out.print("\nPlayer: [" + getPlayer() + "]\n");
    displayBoard();

    System.out.print("\n[1] Select the coin and [2] How many tiles you want to move: ");
    try {
        userCoinNumber = sc.nextInt();
        userTileNumber = sc.nextInt();
    } catch (Exception e) {
        System.out.println("\nInvalid input. Please put an integer number.\n");
        takeATurn();
    }

    if (isValidMove(userCoinNumber, userTileNumber)) {
        setResource(userCoinNumber, userTileNumber);
        if (isGameOver()) {
            displayBoard();
            System.out.println("\nPlayer " + getPlayer() + " is the winner!");
        } else {
            setPlayer(getPlayer());
            takeATurn();
        }
    } else {
        takeATurn();
    }
}

Strengths:

  • Recursive turn handling is clean and avoids unnecessary loops.
  • Input validation ensures the game flow remains smooth.

Opportunities for Improvement:

Avoid Infinite Recursion: Recursive calls in takeATurn() could lead to stack overflow on invalid inputs. Replace with a loop.

Error Handling: Catch input exceptions more gracefully and prompt users again without repeating the entire method.

Improved Code:

public void takeATurn() {
    Scanner sc = new Scanner(System.in);
    while (true) {
        System.out.print("\nPlayer: [" + getPlayer() + "]\n");
        displayBoard();

        System.out.print("\n[1] Select the coin and [2] How many tiles you want to move: ");
        try {
            userCoinNumber = sc.nextInt();
            userTileNumber = sc.nextInt();
        } catch (Exception e) {
            System.out.println("\nInvalid input. Please enter integers only.");
            sc.nextLine(); // Clear the scanner buffer
            continue;
        }

        if (isValidMove(userCoinNumber, userTileNumber)) {
            setResource(userCoinNumber, userTileNumber);
            break;
        }
    }

    if (isGameOver()) {
        displayBoard();
        System.out.println("\nPlayer " + getPlayer() + " is the winner!");
    } else {
        setPlayer(getPlayer());
        takeATurn();
    }
}

Engineering Principles I tried to follow

Modularity: Implements the TwoPlayerGame interface, promoting clean, reusable code.

Dynamic Representation: The use of Vector<E> ensures flexibility in board design and coin placement.

Game Logic: Comprehensive validation prevents illegal moves and ensures a fair game.

Opportunities for Scalability: The current design can be easily extended with additional features (e.g., AI opponent).

Reflection and Recommendations

  • Strengths: The implementation demonstrates clean game logic and proper use of Java's data structures. Recursive methods simplify logic but may need optimization for edge cases.
  • Improvements:
    • Replace recursion in takeATurn() with loops for better scalability.
    • Add comments to explain critical logic blocks for readability.
    • Optimize board generation to avoid redundant iterations.
  • Potential Enhancements: Add an AI player with heuristic-based moves or a graphical interface for a more interactive experience.