aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/fr/umlv/java/wallj/context/Stage.java
blob: 903b5de979107832911eefefca7faaf0a68ce417 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package fr.umlv.java.wallj.context;

import fr.umlv.java.wallj.block.Block;
import fr.umlv.java.wallj.block.BlockFactory;
import fr.umlv.java.wallj.block.BlockType;
import fr.umlv.java.wallj.board.Board;
import fr.umlv.java.wallj.board.BoardConverter;
import fr.umlv.java.wallj.board.TileVec2;
import fr.umlv.java.wallj.event.*;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.World;

import java.util.*;
import java.util.stream.Stream;

/**
 * @author Pacien TRAN-GIRARD
 */
public class Stage implements Updateable {
  public static final int BOMB_PLACEMENTS = 3;
  private static final int VELOCITY_TICK_PER_MS = 6;
  private static final int POSITION_TICK_PER_MS = 2;

  private final World world = new World(new Vec2());
  private final List<Block> blocks = new LinkedList<>();
  private final Board board;
  private boolean running = false;

  /**
   * @param board the base board
   */
  public Stage(Board board) {
    this.board = Objects.requireNonNull(board);
    blocks.addAll(BoardConverter.boardToWorld(board));
    blocks.add(BlockFactory.build(BlockType.ROBOT, findAnyFreeTile(board)));
    blocks.forEach(block -> block.link(world));
  }

  private static TileVec2 findAnyFreeTile(Board board) {
    return board.stream()
           .filter(entry -> entry.getValue() == BlockType.FREE)
           .findAny()
           .map(Map.Entry::getKey)
           .orElseThrow(IllegalArgumentException::new);
  }

  /**
   * @return the JBox2D world
   */
  public World getWorld() {
    return world;
  }

  /**
   * @return the base board
   */
  public Board getBoard() {
    return board;
  }

  /**
   * @return the list of blocks
   */
  public List<Block> getBlocks() {
    return Collections.unmodifiableList(blocks);
  }

  /**
   * @return T(this stage is cleared, i.e. does not contain any garbage block)
   * @implNote TODO: profile this and consider a garbage block counter
   */
  public boolean isCleared() {
    return blocks.stream()
           .noneMatch(block -> block.getType() == BlockType.GARBAGE);
  }

  /**
   * @param context the current context
   * @return the stream of newly generated events
   */
  @Override
  public Stream<Event> update(Context context) {
    return Updateables.updateAll(context,
    this::handleSimulationStartOrder,
    this::handleSimulationStartEvent,
    this::updatePhysicalWorld,
    this::handleBlockDestruction,
    this::handleBlockCreation,
    ctx -> Updateables.updateAll(ctx, blocks));
  }

  private Stream<Event> handleSimulationStartOrder(Context context) {
    return Events.findFirst(context.getEvents(), SimulationStartOrder.class)
           .flatMap(order -> isReady() ? Optional.<Event>of(new SimulationStartEvent()) : Optional.empty())
           .map(Stream::of) // Optional.stream() only available in Java 9
           .orElseGet(Stream::empty);
  }

  private Stream<Event> handleSimulationStartEvent(Context context) {
    Events.findFirst(context.getEvents(), SimulationStartEvent.class)
    .ifPresent(startEvent -> running = true);
    return Stream.empty();
  }

  private Stream<Event> updatePhysicalWorld(Context context) {
    if (running) {
      int dt = (int) context.getTimeDelta().toMillis();
      world.step(dt, dt * VELOCITY_TICK_PER_MS, dt * POSITION_TICK_PER_MS);
    }
    return Stream.empty();
  }

  private Stream<Event> handleBlockDestruction(Context context) {
    Events.filter(context.getEvents(), BlockDestroyEvent.class).forEach(event -> {
      if (blocks.remove(event.getBlock())) event.getBlock().unlink(world);
    });
    return Stream.empty();
  }

  private Stream<Event> handleBlockCreation(Context context) {
    Events.filter(context.getEvents(), BlockCreateEvent.class).forEach(event -> {
      Block block = BlockFactory.build(event.getBlockType(), event.getPos());
      blocks.add(block);
      block.link(world);
    });
    return Stream.empty();
  }

  /**
   * @implNote TODO: profile this and consider a bomb block counter
   */
  private boolean isReady() {
    return blocks.stream()
           .filter(block -> block.getType() == BlockType.BOMB)
           .count() == BOMB_PLACEMENTS;
  }
}