﻿# Sprite games with images and level files

> Arkanoid shows three techniques for the third assignment – sprites from images, key bindings and levels from text files – and the Diver sample shows where they belong: a Swing-free model, levels in resources checked by a playability test, patrolling enemies on a Timer, and a checklist for submitting.

Source: https://laszlonemes.com/university/programming-technology/sprite-games · Author: László Nemes

Session 10 · Programming Technology

Arkanoid shows three techniques for the third assignment – sprites from images, key bindings and levels from text files – and the Diver sample shows where they belong: a Swing-free model, levels in resources checked by a playability test, patrolling enemies on a Timer, and a checklist for submitting.

20 min read `drawImage and sprite sheets` `Key binding styles` `Level files as resources` `Playability check with BFS` `Moves vs ticks` `Third-assignment checklist`

The last session gives you the handholds for the **third assignment**. Two programs: `Arkanoid` is a small, classic brick breaker that shows three techniques clearly – sprites drawn from **images**, the keyboard through **key bindings**, and levels from **text files**. Its code is deliberately simple: game state and drawing live in one class, so the techniques stand out. `Diver` is the opposite: a **complete sample assignment** – a grid game with levels from files, WASD, patrolling enemies with a field of view, lives, a timer and a leaderboard in a database – built in model–view layers, with tests, UML and documentation.

After this session you can

- Draw sprites from image files and know how a sprite sheet is cut into frames.
- Pick the key-binding style that fits your game: velocity, one cell per press, or press/release.
- Load levels from text files packed into the jar, with a strict parser and clear errors.
- Prove with a test that every level is playable, using a breadth-first search.
- Tell what to take from a quick example and what to leave behind, and check your assignment against the list.

> **Take the techniques from Arkanoid, the structure from Diver.** Arkanoid shows *how* to draw an image, bind a key and read a level; Diver shows *where* each of those belongs so the rules can be tested.

## Arkanoid: a small sprite game

Left arrow or `A` starts the paddle moving left, right arrow or `D` moves it right, down arrow or `S` stops it, `Escape` pauses. The ball bounces off the bricks, the walls and the paddle; if it falls out at the bottom, the game starts over. When no bricks are left, the next level comes.

| Class | Job |
| --- | --- |
| `Arkanoid` | entry point |
| `ArkanoidGUI` | the window; its only component is the `GameEngine` |
| `Sprite` | position, size, **image**; `draw(Graphics)` with `drawImage`; `collides` by rectangle intersection |
| `Ball`, `Paddle`, `Brick` | the ball (velocity, bounces at the edges), the paddle (velocity, stops at the edges), a brick (doesn't move) |
| `Level` | the bricks **from a file**; a hit brick disappears; `isOver` when none are left |
| `GameEngine` | the playfield (`JPanel`): images, key bindings, the `Timer`, the game loop `newFrame`, `paintComponent` |

The whole game is one loop. A `Timer` fires roughly 240 times a second and calls `newFrame`, which moves everything one step, applies the rules, and asks for a repaint. Moving the ball in two separate half-steps – first horizontally, then vertically – is a cheap trick worth knowing: a collision after the horizontal step means "bounce sideways", after the vertical step "bounce up or down".

> **Fig. 1** · One frame of Arkanoid. Every rule of the game is somewhere on this path – and all of it runs inside a JPanel.
>
> *Diagram:* Arkanoid's game loop is one method, newFrame, called by a Timer about 240 times a second. It moves the ball horizontally and bounces it off bricks, moves it vertically and restarts if it fell out, bounces off bricks and the paddle, moves the paddle, loads the next level when no bricks are left, and finally asks for a repaint.

GameEngine.newFrame()

```java
private void newFrame() {
    if (!paused) {
        ball.moveX();
        if (level.collides(ball)) {   // hit a brick sideways: bounce horizontally
            ball.invertVelX();
        }
        if (!ball.moveY()) {          // fell out at the bottom: start over
            levelNum = 0;
            restart();
        }
        if (level.collides(ball)) {
            ball.invertVelY();
        }
        if (paddle.collides(ball)) {
            ball.invertVelY();
        }
        paddle.move();
    }
    if (level.isOver()) {             // no bricks left: next level
        levelNum = (levelNum + 1) % LEVEL_COUNT;
        restart();
    }
    repaint();
}
```

## Three techniques worth taking

### 1. Sprites from images

Each image is loaded **once**, and `drawImage` draws it at the sprite's position, stretched to the sprite's size. The images come from a free tile pack (CC0 licence); any PNG works for your own game. The size is decided by the code, not by the file.

```java
// GameEngine constructor – each image is loaded once
background = new ImageIcon("data/images/background.jpg").getImage();
paddleImage = new ImageIcon("data/images/paddle.png").getImage();
ballImage = new ImageIcon("data/images/ball.png").getImage();

// Sprite.draw – the image is stretched to the sprite's size
g.drawImage(image, x, y, width, height, null);
```

Game art often comes as a **sprite sheet**: one big image with many tiles or animation frames on it, plus an atlas file that says where each one is. The tile pack in the Arkanoid folder ships both forms – separate PNGs and a sheet with an XML atlas. The example uses the separate files; with a sheet you load one image and cut rectangles out of it.

Breakout_Tile_Free.xml – the atlas (shortened)

```xml
<TextureAtlas imagePath="Breakout_Tile_Free.png">
  <SubTexture name="02-Breakout-Tiles.png" x="0" y="0" width="384" height="128"/>
  <SubTexture name="03-Breakout-Tiles.png" x="0" y="130" width="384" height="128"/>
  ...
</TextureAtlas>
```

> **Fig. 2** · A sprite sheet plus its coordinates gives you frames. For an animation, cut several frames once at load time and draw a different one on every few ticks.
>
> *Diagram:* A sprite sheet is one image holding many tiles. An atlas file lists where each tile is: x, y, width and height. getSubimage cuts that rectangle out as its own image, which drawImage then draws like any other. The Arkanoid example loads one PNG per brick instead; the tile pack ships both forms.

### 2. Key bindings

The `InputMap` maps a key to a name, the `ActionMap` maps the name to an action. With `WHEN_IN_FOCUSED_WINDOW` the panel doesn't need the keyboard focus – it keeps working after you used the menu, which is exactly where a `KeyListener` fails. A tiny `bind` helper keeps it readable:

GameEngine – bindings

```java
bind("LEFT", "left", () -> paddle.setVelX(-PADDLE_MOVEMENT));
bind("A", "left", () -> paddle.setVelX(-PADDLE_MOVEMENT));
bind("DOWN", "stop", () -> paddle.setVelX(0));
bind("ESCAPE", "pause", () -> paused = !paused);

private void bind(String key, String actionName, Runnable action) {
    getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(key), actionName);
    getActionMap().put(actionName, new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            action.run();
        }
    });
}
```

In Arkanoid a key press *sets a velocity*; the paddle keeps going until you stop it. That is one of three usual styles, and the three example games use one each:

| Style | Example | What the key does | Fits |
| --- | --- | --- | --- |
| velocity | Arkanoid | sets `velX`; the loop moves every frame | smooth movement, "keep going" |
| one cell per press | Diver | `game.move(direction)` once | grid games: one press, one step |
| press / release | Dodger | adds / removes the direction in a `Set`; `tick` moves | move while held, diagonals |

### 3. Levels from text files

A level is a text grid: one line per row of bricks, a digit is the kind of brick, a space is an empty place. The `Level` constructor reads it line by line and computes each brick's pixel position from the character's position.

data/levels/level00.txt (top rows)

```text

  0000000000000000
  1111111111111111
  2222222222222222
  3333333333333333
```

Level – from characters to bricks

```java
int row = 0;
String line;
while ((line = reader.readLine()) != null) {
    for (int col = 0; col < line.length(); col++) {
        char blockType = line.charAt(col);
        if (Character.isDigit(blockType)) {
            Image image = new ImageIcon("data/images/brick0" + blockType + ".png").getImage();
            bricks.add(new Brick(col * BRICK_WIDTH, row * BRICK_HEIGHT,
                    BRICK_WIDTH, BRICK_HEIGHT, image));
        }
    }
    row++;
}
```

> **Fig. 3** · Row and column in the file become y and x on the screen, multiplied by the tile size. The same mapping draws any grid game.
>
> *Diagram:* In an Arkanoid level file every character is a possible brick. A digit is a brick whose image is brick0<digit>.png, a space is no brick. The brick in column 3, row 2 is placed at x = 3 × 40 = 120 and y = 2 × 20 = 40 pixels.

Common mistake

**Levels relative to the working directory**

Arkanoid opens `data/levels/…` relative to the **working directory**, so it only runs when started from the project folder. Start the jar from anywhere else and the level is not found. Diver puts its levels in `src/main/resources/levels`; Maven packs them into the jar, and `getResourceAsStream` finds them wherever the program starts. **Use resources in your assignment.**

```java
try (InputStream in = Level.class.getResourceAsStream("/levels/level01.txt")) {
    if (in == null) {
        throw new IllegalArgumentException("Missing level file");
    }
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
        return parse(reader.lines().toList());
    }
}
```

## What not to copy

`GameEngine` is model (ball, paddle, bricks, rules), view (`paintComponent`) and input handling at once; `Sprite` holds its image and draws itself. For a game this size it fits in one class – but **no test can be written for it**. "If the ball falls out, start over" can only be checked by playing, and the playfield size (800 × 600) is known by several classes.

> **Fig. 4** · The same kind of game, organised two ways. The left one is fine for showing a technique; the right one is what the assignment is graded against.
>
> *Diagram:* In Arkanoid one class, GameEngine, holds the rules, the drawing, the keyboard input and the game loop, and every Sprite keeps its image and draws itself, so nothing can be tested without a window. The Diver sample splits the same kind of game into a view package, a model package with no Swing and no SQL, and a db package that implements the model's repository interface.

- No `Image` in the model. The model has numbers (position, size, kind); the view picks the picture.
- No `draw(Graphics)` in model classes. The view reads the model and draws it.
- One owner for the playfield size – the model – and everyone asks it.
- The rules in a method like `tick()` or `move()` that a test can call.

## Diver: the sample third assignment

A diver collects pearls in a rocky underwater cave. The cave is a grid of rock (impassable) and water; some water cells hold a pearl. The diver starts at the boat (the start cell) and moves one cell per `W`/`A`/`S`/`D` press (or arrow). A pearl is worth one point. Sharks patrol in straight lines, horizontally or vertically, turning at rock and at the edge. A shark sees its own cell and the one in front of it; if the diver is in a shark's field of view, a life is lost and the diver goes back to the boat. With all pearls collected the next level comes – there are 10, in files. When the 3 lives are gone, the program asks for a name, saves the score to the database and shows the top 10.

Good to know

**Why not one of the assigned tasks?**

The assignment tasks (Yogi Bear, Snake, Labyrinth, Tron) are deliberately not solved here. Diver is a *similar* game: the same techniques and the same structure, different rules. Take the layout, the testing approach and the shape of the documentation – the rules of your own task are yours to write.

### The rules, and where they live

| # | Rule | Where |
| --- | --- | --- |
| 1 | The level is a grid of rock, water and pearls; start and shark positions come from the file | `Tile`, `Level.parse` |
| 2 | 10 levels from files, strictly checked (equal lines, exactly one start, at least one pearl, known symbols) | `Level.load`, `Level.parse` |
| 3 | Every level is playable: every pearl can be reached from the start | `Level.isPlayable` (BFS), a test over all 10 |
| 4 | The diver moves one cell in four directions; not onto rock, not off the board | `Game.move` |
| 5 | Stepping on a pearl collects it: +1 point, the cell becomes water | `Game.move` |
| 6 | A shark steps every SHARK_PERIOD-th tick; at rock or the edge it turns and does not step | `Shark.patrol`, `Game.tick` |
| 7 | A shark sees its own cell and VISION = 1 cell ahead, not through rock | `Shark.sees` |
| 8 | Diver seen (after a move or a shark step): −1 life, back to the start | `Game.checkSharks` |
| 9 | 0 lives: game over; after that move and tick do nothing | `Game.move`, `Game.tick` |
| 10 | After the last pearl the next level; after the last level the first; score and lives stay | `Game.move` → `startLevel` |
| 11 | Time since the start, counted in ticks; stops at game over | `Game.getElapsedMillis` |
| 12 | At the end: name, save, top 10; new game from the menu | `DiverGUI.gameOver`, `Leaderboard` |

Two questions the task leaves open, and the sample answers explicitly in its documentation – yours should do the same for its open questions:

- **What is "one unit"?** One grid cell. The field of view is a constant (`Shark.VISION = 1`); a larger one is a single number to change (plus the tests).
- **When do we check for being caught?** After every diver move and every shark step – nothing else can change the situation in between. The levels are drawn so that no shark sees the start, and a test checks it.

### The structure

Three packages, dependencies pointing one way, as in the Dodger of the previous session: `diver.view` (Swing) calls `diver.model` (no Swing, no AWT, no SQL), and `diver.db` implements the model's `HighScoreRepository` with JDBC. `main` wires them together: `new DiverGUI(new Leaderboard(new JdbcHighScoreRepository()))`.

One design decision is worth copying directly: **the `Level` never changes during play; the `Game` works on copies.** A collected pearl becomes water in the game's own tile array, and the sharks are fresh objects. So the same `Level` can be restarted any number of times, and the 10 levels are loaded only once.

Game.startLevel

```java
private void startLevel(int index) {
    levelIndex = index;
    level = levels.get(index);
    tiles = level.copyTiles();      // the Game's own copy: a collected pearl becomes water here
    diver = level.getStart();
    sharks = level.newSharks();     // fresh Shark objects at their start positions
    pearlsLeft = level.getPearlCount();
}
```

## Levels from files, checked by a test

src/main/resources/levels/level01.txt

```text
############
#D.........#
#.###.###..#
#.#P....P#.#
#.#.####.#.#
#.P..H...P.#
#..........#
############
```

> **Fig. 5** · The parser turns characters into three things: a grid of tiles, a start position and a list of sharks. D, H and V are markers on top of water.
>
> *Diagram:* Level.parse reads the level file character by character. Hash is rock, dot is water, P is a pearl. D marks the start, H a horizontally patrolling shark facing right, V a vertical one facing down; under D, H and V there is water. The result is a Tile grid, a start position and a list of sharks.

The parser is **strict** and reports every problem with its line number: unequal line lengths, an unknown symbol, a missing or second start, no pearl. A broken level file shows up while you develop, not on the player's machine.

Level.parse – the heart of it

```java
switch (c) {
    case '#' -> tiles[row][col] = Tile.ROCK;
    case '.' -> tiles[row][col] = Tile.WATER;
    case 'P' -> { tiles[row][col] = Tile.PEARL; pearls++; }
    case 'D' -> { /* exactly one start, water underneath */ }
    case 'H' -> { sharks.add(new Shark(position, Direction.RIGHT)); tiles[row][col] = Tile.WATER; }
    case 'V' -> { sharks.add(new Shark(position, Direction.DOWN)); tiles[row][col] = Tile.WATER; }
    default -> throw new IllegalArgumentException("Line " + (row + 1) + ", character "
            + (col + 1) + ": unknown symbol '" + c + "'");
}
```

The task requires every level to be playable. Don't judge that by eye – let the program check it. `isPlayable()` runs a **breadth-first search** from the start over passable cells and then asks whether every pearl was visited.

> **Fig. 6** · Breadth-first search spreads from the start one step at a time. Whatever it never touches can't be reached by the player either.
>
> *Diagram:* isPlayable walks breadth-first from the start D over every passable cell; the numbers are the order of discovery by distance. Every pearl it reaches is collectable. The pearl at the bottom is walled in by rock, the walk never reaches it, so this level is not playable.

Level.isPlayable – the search

```java
Set<Position> visited = new HashSet<>();
Queue<Position> queue = new ArrayDeque<>();
queue.add(start);
visited.add(start);
while (!queue.isEmpty()) {
    Position current = queue.remove();
    for (Direction direction : Direction.values()) {
        Position next = current.step(direction);
        if (contains(next) && getTile(next).isPassable() && visited.add(next)) {
            queue.add(next);
        }
    }
}
// playable if every pearl's position is in visited
```

LevelTest – every built-in level

```java
@Test
public void allBuiltInLevelsLoadAndArePlayable() {
    List<Level> levels = Level.loadAll();
    assertEquals(Level.LEVEL_COUNT, levels.size());
    for (int i = 0; i < levels.size(); i++) {
        assertTrue("level " + (i + 1) + ": is playable", levels.get(i).isPlayable());
        // ... has pearls, has sharks, no shark sees the start
    }
}
```

In your assignment

**Level files in your game**

- At least 10 levels in `src/main/resources/levels/` (unless an algorithm generates the playfield), read with `getResourceAsStream`.
- A strict parser: equal line lengths, the right number of start cells, something to collect, known symbols – errors name the line.
- A test that loads all 10 and checks they are playable (BFS from the start). For Yogi Bear: every basket reachable from the entrance.
- If levels differ in size, call `frame.pack()` when the level changes.

## Sharks, moves and ticks

A shark's state is two values: its position and its direction. `patrol` is one step; turning "costs" the step – at rock the shark stops, turns, and leaves the next time. Simple, and the player can see it happen.

Shark.patrol

```java
void patrol(Level level) {
    Position next = position.step(direction);
    if (level.contains(next) && level.getTile(next).isPassable()) {
        position = next;
    } else {
        direction = direction.opposite();   // turning costs this step
    }
}
```

`sees` walks forward cell by cell up to `VISION`, stopping at rock or the edge, so the constant can grow without rewriting the method:

Shark.sees

```java
boolean sees(Position target, Level level) {
    if (position.equals(target)) {
        return true;
    }
    Position ahead = position;
    for (int distance = 1; distance <= VISION; distance++) {
        ahead = ahead.step(direction);
        if (!level.contains(ahead) || !level.getTile(ahead).isPassable()) {
            return false;                   // rock or edge: the view stops here
        }
        if (ahead.equals(target)) {
            return true;
        }
    }
    return false;
}
```

> **Fig. 7** · Red cells are what the shark sees. The view draws exactly this area in pale red, so the player understands why they were caught.
>
> *Diagram:* With VISION = 1 a shark sees its own cell and the one in front of it. A diver two cells ahead is safe. A rock in front stops the view, so the shark sees only its own cell. When patrol finds rock or the edge ahead, the shark turns around and does not move in that step; it moves away the next time.

The game has **two clocks**. The diver moves when the player presses a key – that doesn't depend on time. The sharks move with time: the view's `Timer` calls `tick` every 50 ms, and every 10th tick (half a second) the sharks take a step. Both paths end in `checkSharks`.

> **Fig. 8** · One step = one move(), one beat = one tick(). The model is where the two meet, so a test can drive either one.
>
> *Diagram:* The diver moves when a key is pressed, at any moment. The Timer calls tick every 50 ms, and every 10th tick the sharks patrol one cell. Both a move and a shark step end in checkSharks, which costs a life and sends the diver back to the start if a shark sees it.

Game.tick

```java
public void tick() {
    if (over) {
        return;
    }
    ticks++;
    if (ticks % SHARK_PERIOD == 0) {
        for (Shark shark : sharks) {
            shark.patrol(level);
        }
        checkSharks();
    }
}
```

GamePanel – one cell per key press

```java
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
        .put(KeyStroke.getKeyStroke("pressed " + key), "move-" + key);
getActionMap().put("move-" + key, new AbstractAction() {
    @Override
    public void actionPerformed(ActionEvent e) {
        game.move(direction);    // the model decides: rock means no step
        repaint();
    }
});
```

The elapsed time is `ticks × TICK_MS`, not the system clock. So in a test time "passes" when you call `tick`; it stops at game over; and while a modal dialog is open (the `Timer` is stopped) the time stops too.

## Events and tests

| Event | Source | Handler | Action |
| --- | --- | --- | --- |
| W / A / S / D (and arrows) pressed | `GamePanel` key binding | `AbstractAction` in `bindKeys` | `game.move(direction)`, `repaint()` |
| Timer fires (50 ms) | `DiverGUI.timer` | `tick()` | `game.tick()`; `pack()` on a new level; repaint; status bar; at the end `timer.stop()`, `gameOver()` |
| Game ▸ New game (Ctrl+N) | `JMenuItem` | `newGame()` | new `Game`, `gamePanel.setGame`, `pack()`, `timer.restart()` |
| Game ▸ High scores (Ctrl+H) | `JMenuItem` | `showHighScores()` | timer stops; `leaderboard.top(10)` → dialog; error → message; timer starts |
| Game ▸ Exit (Ctrl+Q) | `JMenuItem` | lambda | `System.exit(0)` |
| Game over (from tick) | – | `gameOver()` | `askName()`, `leaderboard.save`, `showHighScores()`, `newGame()` |

53 JUnit tests in four classes, all without Swing, the game tests without a database:

| Class | Tests | What it checks |
| --- | --- | --- |
| `LevelTest` | 12 | parsing (tiles, start, sharks, water under markers); copies independent of the Level; broken levels rejected with the line; isPlayable true and false; all 10 built-in levels load, are playable, no shark sees the start |
| `GameTest` | 21 | movement (water, rock, edge); collecting pearls; next level and wrap-around; sharks step on ticks, turn at rock and edge, swim over pearls; caught by stepping in front or by a shark stepping next to you; no view through rock or sideways; game over; time in ticks |
| `LeaderboardTest` | 11 | the leaderboard backend with an in-memory repository |
| `JdbcHighScoreRepositoryTest` | 9 | the JDBC repository on a temporary SQLite file |

The game tests give their levels **as text**, right in the test. You see the situation at a glance, and no file is needed:

GameTest – a level as five strings

```java
@Test
public void walkingInFrontOfASharkCostsALifeAndRespawns() {
    Game game = game(
            "#####",
            "#D..#",
            "#.H.#",       // the shark faces right: it sees (2, 3)
            "#.P.#",
            "#####");
    game.move(Direction.RIGHT);
    game.move(Direction.RIGHT);         // (1, 3): safe
    game.move(Direction.DOWN);          // (2, 3): in front of the shark

    assertEquals(Game.LIVES - 1, game.getLives());
    assertEquals(new Position(1, 1), game.getDiver());
}
```

A helper `sharkStep(game)` calls `tick()` `SHARK_PERIOD` times, so "the shark moves one cell" reads as one line.

## The third assignment: checklist

Go through this before you submit – the right column shows where the sample solves it.

| Requirement | In the sample |
| --- | --- |
| Basic graphics; the player's sprite moves with WASD | `GamePanel.paintComponent`; key binding W/A/S/D → `Game.move` |
| Unless an algorithm generates the playfield: at least 10 levels in files, all playable | `levels/level01..10.txt`; `Level.isPlayable`; the all-levels test |
| A timer showing the time since the start | `Game.getElapsedMillis` (from ticks); the status bar |
| At the end a pop-up asks for the name; the score goes into a database | `DiverGUI.gameOver`, `askName`, `Leaderboard.save` |
| A menu item for the top 10 | Game ▸ High scores: `Leaderboard.top(10)`, `HighScoresDialog` |
| New game from another menu item | Game ▸ New game |
| Object-oriented; layered (recommended) | `model` / `db` / `view` packages |
| User-friendly | status bar, shortcuts, the sharks' field of view drawn, a database error is a message, not a crash |
| Documentation: description, analysis, structure with a UML class diagram, implementation chapter, event–handler pairs | the sample's README, chapters 1–5 |
| Tests (asked at the presentation) | `LevelTest`, `GameTest`, `LeaderboardTest`, `JdbcHighScoreRepositoryTest` |

In your assignment

**The documentation**

Hand it in as a **DOCX or PDF** file. The sample's description is Markdown only because it is read on GitHub – what counts is the content: the task and its analysis (including the questions it left open and how you answered them), the structure with a UML class diagram, an implementation chapter for the interesting parts (level loading and the playability check, the enemies' rules, the timing), event–handler pairs, sequence and state diagrams, tests, how to run it. Draw the diagrams with any tool – draw.io, StarUML, PlantUML, Mermaid, or by hand and scanned – but they must match the code.

In your assignment

**What the presentation asks**

The questions are about **your own code**. Be ready to answer:

- Why is this rule in this class?
- What does this test check, and what would make it fail?
- What happens when the database is not available?
- How does a level get from the file onto the screen?

If you follow the sample's structure, each of these has a one-sentence answer.

In your assignment

**Order of work**

1. The level format, the parser and the playability test.
2. The `Game` with its rules and tests, levels given as text in the tests.
3. The enemies' rules in the model, with tests: where they go, when they turn, what they see.
4. The view last: `paintComponent` reads the model cell by cell (`getTile`, `getDiver`, `getSharks`) and stores nothing about the game.
5. The leaderboard from the previous session, unchanged except for what the row contains.

## Practice

Task 1

**Arkanoid levels from resources**

Move Arkanoid's level files to `src/main/resources/levels/` and load them with `getResourceAsStream`, so the jar runs from any folder. Do the same for the images.

**Approach:**

`Level` needs a constructor that takes the lines (or a `BufferedReader`) instead of a `Path`. Remember that `getResourceAsStream` returns `null` for a missing file – turn that into a clear exception. For images, `ImageIO.read(getClass().getResource("/images/ball.png"))`.

```java
// instead of Path.of("data", "levels", ...) and Files.newBufferedReader:
String name = String.format("/levels/level%02d.txt", levelNum);
InputStream in = GameEngine.class.getResourceAsStream(name);   // null if missing!
```

Task 2

**Cut a sprite sheet**

Load `Breakout_Tile_Free.png` once and draw the bricks with tiles cut out of it, using the coordinates in the XML atlas, instead of the separate PNG files.

**Approach:**

Cut every frame **once** at start-up into an array, not in `paintComponent`. You can copy the few coordinates you need into a constant table rather than parsing the XML.

```java
BufferedImage sheet = ImageIO.read(getClass().getResource("/images/Breakout_Tile_Free.png"));
BufferedImage tile = sheet.getSubimage(0, 130, 384, 128);   // x, y, width, height from the XML
```

Task 3

**The all-levels-playable test for your format**

Write the test that loads all levels of your own assignment and checks that each one can be finished. Then break one level file on purpose and watch the test fail with a message naming the level.

**Approach:**

Breadth-first search from the start over passable cells (a `Set` of visited positions and an `ArrayDeque` as the queue), then check that every collectable cell is in the visited set. Put the level number into the assertion message.

Task 4

**Pull Arkanoid's rules out of the panel**

Create a Swing-free model for Arkanoid: a `Game` with ball, paddle and bricks as plain numbers, a `tick()` with the rules of `newFrame`, and queries for the view. Then write a test for "if the ball falls out, the level restarts".

**Approach:**

Model classes keep only positions, sizes and velocities – no `Image`, no `Graphics`, `java.awt.Rectangle` replaced by your own intersection check. The panel keeps the images and draws by asking the model. The playfield size lives in the model only.

```java
// a Swing-free ball: numbers only, no Image, no Graphics
public final class Ball {
    private int x, y, velX = 1, velY = 1;
    // move, bounce ... the view draws it with drawImage at (getX(), getY())
}
```

Task 5

**A sharper-eyed shark**

Set `Shark.VISION` to 2. Which tests in `GameTest` and `LevelTest` change their outcome, and why? Does the view's pale red area follow automatically?

**Where to look:**

The "diver two cells away is safe" situations now get caught; levels where a shark is two cells from the start fail the "no shark sees the start" check. The view draws the field of view with its own loop – check whether it uses `VISION` or a literal 1.

## Summary

- **Sprite** – a moving picture in the game; in the model a rectangle, in the view an image
- **Sprite sheet** – one image with many frames; an atlas lists their rectangles, getSubimage cuts them
- **Key binding** – InputMap (key → name) + ActionMap (name → action); works without focus
- **Resource** – a file packed into the jar, read with `getResourceAsStream` – independent of the working directory
- **BFS** – breadth-first search: visits everything reachable from a start, nearest first

- Load each image once; drawImage stretches it to the sprite's size. A sprite sheet is cut with getSubimage.
- Choose the key style that fits: velocity, one step per press, or a set of held directions.
- Levels are text grids in src/main/resources, parsed strictly, with a test that every level is playable.
- Arkanoid shows the techniques; its one-class structure is not something to copy.
- Diver is the shape of a finished third assignment: model / db / view, rules in move and tick, 53 tests, DOCX or PDF documentation.
