# 4-B: 2048

**Learning Targets**

* I can create and initialize 2D arrays of primitives.
* I can traverse 2D arrays using nested loops in both row-major and column-major order.
* I can implement algorithms that operate on entire rows or columns.
* I can manipulate array contents by shifting, merging, or transforming values.

### From Pictures to Grids

In the PicLab project, you worked with 2D arrays of Pixel objects to manipulate images. Each pixel had a location (row, column) and properties like color. Now we're going to work with 2D arrays of primitives—specifically integers—to represent game boards, data tables, and other grid-based structures.

The concepts are similar:

* **PicLab:** A 2D array where each cell holds a Pixel object
* **Grid games:** A 2D array where each cell holds an integer representing game state

The difference? Instead of calling methods on Pixel objects, we'll be performing calculations and comparisons directly on integer values.

### Understanding 2D Arrays

A 2D array is like a table with rows and columns. Think of a classroom seating chart, a chess board, or a spreadsheet—any time you have data organized in a grid, a 2D array is a natural fit.

#### Declaration and Initialization

Here's how you create a 2D array:

```java
// Create a 4x4 grid of integers, all initialized to 0
int[][] grid = new int[4][4];

// You can also initialize with values
int[][] scores = {
    {90, 85, 88, 92},
    {78, 91, 84, 87},
    {95, 89, 93, 90}
};
```

**Important terminology:**

* `grid.length` gives you the number of rows (3 in the scores example)
* `grid[0].length` gives you the number of columns (4 in the scores example)
* `grid[row][col]` accesses the element at that position

#### Visualizing 2D Arrays

Let's say you have this array:

````java
int[][] numbers = {
    {2, 4, 6, 8},
    {1, 3, 5, 7},
    {10, 20, 30, 40}
};
```

You can visualize it as:
```
     col0  col1  col2  col3
row0:  2    4     6     8
row1:  1    3     5     7
row2:  10   20    30    40
````

To access the value `30`, you'd use `numbers[2][3]`.

**Think of it this way:** The first index is "which row?" and the second index is "which seat in that row?"

### Traversing 2D Arrays

The most fundamental skill with 2D arrays is visiting every cell. You do this with nested loops.

#### Row-Major Traversal

This is the most common pattern—go through each row, and for each row, visit every column:

```java
public void printGrid(int[][] grid) {
    for (int row = 0; row < grid.length; row++) {
        for (int col = 0; col < grid[row].length; col++) {
            System.out.print(grid[row][col] + " ");
        }
        System.out.println();  // New line after each row
    }
}
```

**Pattern:** Outer loop controls rows, inner loop controls columns.

#### Column-Major Traversal

Sometimes you need to process by columns instead. For example, finding the highest score in each subject:

```java
public void printColumnTotals(int[][] scores) {
    for (int col = 0; col < scores[0].length; col++) {
        int total = 0;
        for (int row = 0; row < scores.length; row++) {
            total += scores[row][col];
        }
        System.out.println("Column " + col + " total: " + total);
    }
}
```

**Pattern:** Outer loop controls columns, inner loop controls rows.

**This is crucial:** Notice how the indices swap. When traversing by column, `scores[row][col]` still uses row first, but your outer loop is the column counter.

### Common 2D Array Patterns

#### Finding Values

Let's say you want to find if a specific value exists in the grid:

```java
public boolean contains(int[][] grid, int target) {
    for (int row = 0; row < grid.length; row++) {
        for (int col = 0; col < grid[row].length; col++) {
            if (grid[row][col] == target) {
                return true;
            }
        }
    }
    return false;
}
```

Or find the location of the maximum value:

```java
public int[] findMax(int[][] grid) {
    int maxRow = 0;
    int maxCol = 0;
    int maxValue = grid[0][0];
    
    for (int row = 0; row < grid.length; row++) {
        for (int col = 0; col < grid[row].length; col++) {
            if (grid[row][col] > maxValue) {
                maxValue = grid[row][col];
                maxRow = row;
                maxCol = col;
            }
        }
    }
    
    return new int[]{maxRow, maxCol};  // Return coordinates
}
```

#### Collecting Matching Cells

Sometimes you need to find all cells that meet certain criteria. For example, finding all empty cells in a game board:

```java
public ArrayList<int[]> findEmptyCells(int[][] board) {
    ArrayList<int[]> empty = new ArrayList<>();
    
    for (int row = 0; row < board.length; row++) {
        for (int col = 0; col < board[row].length; col++) {
            if (board[row][col] == 0) {
                // Store coordinates as an array
                empty.add(new int[]{row, col});
            }
        }
    }
    
    return empty;
}
```

**Pattern:** Use an ArrayList to accumulate results, storing coordinates as `int[]` arrays.

### Operating on Rows vs Columns

Many grid-based algorithms need to process entire rows or columns as units. This is where things get interesting.

#### Processing a Single Row

Let's say you want to shift all non-zero values in a row to the left:

```java
public void compressRow(int[][] grid, int row) {
    int writePos = 0;  // Where to write the next non-zero value
    
    // Collect all non-zero values
    for (int col = 0; col < grid[row].length; col++) {
        if (grid[row][col] != 0) {
            grid[row][writePos] = grid[row][col];
            writePos++;
        }
    }
    
    // Fill remaining positions with zeros
    while (writePos < grid[row].length) {
        grid[row][writePos] = 0;
        writePos++;
    }
}
```

**Example:** `[0, 3, 0, 5, 0, 2]` becomes `[3, 5, 2, 0, 0, 0]`

#### Processing a Single Column

The same operation on a column requires careful index management:

```java
public void compressColumn(int[][] grid, int col) {
    int writePos = 0;
    
    // Collect all non-zero values
    for (int row = 0; row < grid.length; row++) {
        if (grid[row][col] != 0) {
            grid[writePos][col] = grid[row][col];
            writePos++;
        }
    }
    
    // Fill remaining positions with zeros
    while (writePos < grid.length) {
        grid[writePos][col] = 0;
        writePos++;
    }
}
```

**Notice the difference:** When working with rows, `col` changes but `row` is fixed. When working with columns, `row` changes but `col` is fixed.

#### Processing All Rows or Columns

Once you can process a single row or column, you can loop through all of them:

```java
// Compress all rows to the left
public void compressAllRowsLeft(int[][] grid) {
    for (int row = 0; row < grid.length; row++) {
        compressRow(grid, row);
    }
}

// Compress all columns upward
public void compressAllColumnsUp(int[][] grid) {
    for (int col = 0; col < grid[0].length; col++) {
        compressColumn(grid, col);
    }
}
```

### Advanced Pattern: Merging Adjacent Values

A common requirement in grid games is merging adjacent equal values. Here's how you might merge adjacent matching numbers in a row:

```java
public void mergeRow(int[][] grid, int row) {
    // First compress to remove gaps
    compressRow(grid, row);
    
    // Then merge adjacent equal values
    for (int col = 0; col < grid[row].length - 1; col++) {
        if (grid[row][col] != 0 && grid[row][col] == grid[row][col + 1]) {
            grid[row][col] *= 2;      // Double the value
            grid[row][col + 1] = 0;   // Clear the merged cell
        }
    }
    
    // Compress again to remove new gaps
    compressRow(grid, row);
}
```

**Example:** `[2, 2, 4, 4, 0, 0]` becomes `[4, 8, 0, 0, 0, 0]`

This pattern of compress → process → compress is very common in grid manipulation algorithms.

### Putting It All Together

When working with 2D arrays in games or simulations, you typically:

1. **Initialize the grid** with starting values
2. **Implement row/column operations** as helper methods
3. **Apply operations based on user input** or game rules
4. **Check win/loss conditions** by traversing the grid
5. **Update display** by traversing and rendering

The key skills are:

* Traversing correctly (row-major vs column-major)
* Keeping indices straight (row first, then column)
* Operating on rows vs columns (which index is fixed?)
* Using helper methods to break complex operations into steps

### 2048 Project

You're now ready to build a fully functional 2048 game! In this project, you'll:

* Manage a 4x4 grid of integers representing tiles
* Implement sliding and merging logic for all four directions (up, down, left, right)
* Add random tiles after each move
* Check for win conditions (reaching 2048) and game over (no valid moves)
* Handle complex array manipulation with multiple passes

The game combines everything you've learned about 2D arrays:

* Creating and initializing grids
* Traversing by rows and columns
* Operating on entire rows or columns
* Checking conditions across the board
* Modifying arrays in place

**You'll get a GitHub Classroom link to set up the project.** The repository includes a complete game GUI, and your job is to implement the core game logic. The README provides detailed guidance on each method you need to write, along with the algorithms and patterns to use.

This is one of the most satisfying projects in the course—when your move methods work correctly and you see the tiles sliding and merging on screen, you'll have built a real, playable game using fundamental CS concepts!

Good luck! 🎮
