Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repositories {
}

group = "ru.otus.main_patterns"
version = "1.0.11-SNAPSHOT"
version = "1.0.12-SNAPSHOT"

application {
mainClass.set("ru.otus.main_patterns.App")
Expand Down
15 changes: 0 additions & 15 deletions app/src/main/java/ru/otus/main_patterns/App.java

This file was deleted.

12 changes: 12 additions & 0 deletions app/src/main/java/ru/otus/main_patterns/hw12/Cell.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.otus.main_patterns.hw12;

import java.util.ArrayList;
import java.util.List;

public class Cell {
private final List<GameObject> objects = new ArrayList<>();

public List<GameObject> getObjects() {
return objects;
}
}
59 changes: 59 additions & 0 deletions app/src/main/java/ru/otus/main_patterns/hw12/GameObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ru.otus.main_patterns.hw12;

import java.util.HashSet;
import java.util.Set;

public class GameObject {
private int id;
private int x, y;
private int size;

private Set<Cell> cells = new HashSet<>();

public GameObject(int id, int x, int y, int size) {
this.id = id;
this.x = x;
this.y = y;
this.size = size;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public int getSize() {
return size;
}

public void setSize(int size) {
this.size = size;
}

public Set<Cell> getCells() {
return cells;
}

public void setCells(Set<Cell> cells) {
this.cells = cells;
}
}
53 changes: 53 additions & 0 deletions app/src/main/java/ru/otus/main_patterns/hw12/Grid.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package ru.otus.main_patterns.hw12;

import java.util.*;

public class Grid {
private final int cellSize;
private final int offsetX;
private final int offsetY;

private final Map<String, Cell> cells = new HashMap<>();

public Grid(int cellSize, int offsetX, int offsetY) {
this.cellSize = cellSize;
this.offsetX = offsetX;
this.offsetY = offsetY;
}

private String key(int x, int y) {
return x + "_" + y;
}

private int cellX(int x) {
return (x + offsetX) / cellSize;
}

private int cellY(int y) {
return (y + offsetY) / cellSize;
}

public Set<Cell> getCellsForObject(GameObject obj) {
Set<Cell> result = new HashSet<>();

int minX = cellX(obj.getX() - obj.getSize());
int maxX = cellX(obj.getX() + obj.getSize());
int minY = cellY(obj.getY() - obj.getSize());
int maxY = cellY(obj.getY() + obj.getSize());

for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
result.add(getCell(x, y));
}
}
return result;
}

private Cell getCell(int x, int y) {
return cells.computeIfAbsent(key(x, y), k -> new Cell());
}

public Collection<Cell> getAllCells() {
return cells.values();
}
}
111 changes: 111 additions & 0 deletions app/src/main/java/ru/otus/main_patterns/hw12/HW12.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ru.otus.main_patterns.hw12;

import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.otus.main_patterns.hw12.command.MacroCommand;
import ru.otus.main_patterns.hw12.command.UpdateObjectCellCommand;

/*
Домашнее задание
Реализация проверки коллизий игровых объектов

Цель:
Научится применять цепочку обязанности

Описание/Пошаговая инструкция выполнения домашнего задания:
Предположим, что у нас есть функция/процедура/метод, который умеет для двух игровых объектов
определять коллизию - столкнулись эти два объекта или нет.
Эту функцию/процедуру/метод реализовывать не нужно, так это достаточно большая задача.
Используя эту функцию, реализуем эффективный перебор всех игровых объектов, для определения всех возможных коллизий
в игре в каждый момент времени.
Для этого разобьем игровое поле на окрестности, предполагая, что объекты, находящиеся в разных окрестностях
не могут столкнуться друг с другом. Столкнуться друг с другом могут только объекты из одной окрестности.

Необходимо:
1. Реализовать команду, которая
- определяет окрестность, в которой присутствует игровой объект
- если объект попал в новую окрестность, то удаляет его из списка объектов старой окрестности и добавляет список
объектов новой окрестности.
- для каждого объекта новой окрестности и текущего движущегося объекта создает команду проверки коллизии этих двух объектов.
Все эти команды помещает в макрокоманду и эту макрокоманду записывает на место аналогичной макрокоманды для предыдущей окрестности.
2. При подходе с окрестностями может оказаться, что объекты, находящиеся рядом, могут попасть в разные окрестности,
что не позволит определить коллизию между ними.
Для решения этой проблемы будем использовать две системы окрестностей - одни окрестности смещены относительно центра других окрестностей.
Для этого можно использовать несколько команд из п. 1
3. С помощью команды из 1. организовать проверку коллизий игровых объектов с помощью двух систем окрестностей.

Критерии оценки:
1. Задача сдана на проверку. 1 балл
2. Задача оформлена в виде MR/PR. 1 балл
3. CI - 1 балл
4. Реализована команда из п.1
1 балл, если реализация правильно формирует список игровых объектов для каждой окрестности
2 балла, если написана полная реализация
5. Реализованы тесты к команде из п.1
1 балл, если написан хотя бы один тест
2 балла, если преподаватель не может указать кейс, который не покрыт тестами.
6. Реализован механизм с произвольным количеством систем окрестностей из п.3 2 балла
7. Написаны тесты к п.3 1 балл

Максимальная оценка за задание 10 баллов
Задание принимается, если было оценено не менее, чем в 7 баллов

Решение:
Полный pipeline выполнения:
Game Tick
UpdateObjectCellCommand (для каждого объекта)
объект перемещается между клетками
создаются CollisionCommand (для пар объектов)
MacroCommand.execute()
CollisionCommand.execute()
handler.handle(a, b) -> Chain of Responsibility

*/
public class HW12 {
private static final Logger logger = LoggerFactory.getLogger(HW12.class);

public static void main(String[] args) {
logger.info(
"Java version: {}, Java vendor: {}\nДомашнее задание#12: Реализация проверки коллизий игровых объектов",
System.getProperty("java.version"),
System.getProperty("java.vendor"));
int cellSize = 50;

// 2 сетки (смещение!)
Grid grid1 = new Grid(cellSize, 0, 0);
Grid grid2 = new Grid(cellSize, cellSize / 2, cellSize / 2);

List<Grid> grids = Arrays.asList(grid1, grid2);

MacroCommand macro = new MacroCommand();

List<GameObject> objects =
Arrays.asList(
new GameObject(1, 10, 10, 10),
new GameObject(2, 20, 20, 10),
new GameObject(3, 55, 55, 10),
new GameObject(4, 60, 60, 10),
new GameObject(5, 100, 100, 10),
new GameObject(6, 110, 105, 10),
new GameObject(7, 200, 200, 10),
new GameObject(8, 205, 205, 10),
new GameObject(9, 300, 300, 10),
new GameObject(10, 305, 300, 10));

// симуляция одного тика
for (GameObject obj : objects) {
new UpdateObjectCellCommand(obj, grids, macro).execute();
}

// выполнение всех коллизий
macro.execute();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ru.otus.main_patterns.hw12.command;

import ru.otus.main_patterns.hw12.GameObject;
import ru.otus.main_patterns.hw12.handler.Handler;
import ru.otus.main_patterns.hw12.interfaces.Command;

public class CollisionCommand implements Command {
private final GameObject gameObjectA;
private final GameObject gameObjectB;
private final Handler handler;

public CollisionCommand(GameObject gameObjectA, GameObject gameObjectB, Handler handler) {
this.gameObjectA = gameObjectA;
this.gameObjectB = gameObjectB;
this.handler = handler;
}

@Override
public void execute() {
handler.handle(gameObjectA, gameObjectB);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ru.otus.main_patterns.hw12.command;

import java.util.ArrayList;
import java.util.List;
import ru.otus.main_patterns.hw12.interfaces.Command;

public class MacroCommand implements Command {
private final List<Command> commands = new ArrayList<>();

public void add(Command c) {
commands.add(c);
}

public void clear() {
commands.clear();
}

public List<Command> getCommands() {
return commands;
}

@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ru.otus.main_patterns.hw12.command;

import java.util.List;
import java.util.Set;
import ru.otus.main_patterns.hw12.Cell;
import ru.otus.main_patterns.hw12.GameObject;
import ru.otus.main_patterns.hw12.Grid;
import ru.otus.main_patterns.hw12.handler.CollisionCheckHandler;
import ru.otus.main_patterns.hw12.handler.DamageHandler;
import ru.otus.main_patterns.hw12.handler.Handler;
import ru.otus.main_patterns.hw12.interfaces.Command;

/*
1. Удаляет объект из старых клеток
2. Добавляет в новые клетки (из двух grid)
3. Генерирует CollisionCommand в MacroCommand
*/
public class UpdateObjectCellCommand implements Command {
private final GameObject gameObject;
private final List<Grid> grids;
private final MacroCommand macroCommand;

public UpdateObjectCellCommand(
GameObject gameObject, List<Grid> grids, MacroCommand macroCommand) {
this.gameObject = gameObject;
this.grids = grids;
this.macroCommand = macroCommand;
}

@Override
public void execute() {
// 1. удалить из старых клеток
for (Cell cell : gameObject.getCells()) {
cell.getObjects().remove(gameObject);
}
gameObject.getCells().clear();

// 2. добавить в новые клетки (из всех grid)
for (Grid grid : grids) {
Set<Cell> newCells = grid.getCellsForObject(gameObject);
for (Cell cell : newCells) {
// 3. создать команды коллизий
for (GameObject other : cell.getObjects()) {
// Chain of Responsibility
Handler handler = buildChain(this.gameObject);
macroCommand.add(new CollisionCommand(this.gameObject, other, handler));
}
cell.getObjects().add(gameObject);
gameObject.getCells().add(cell);
}
}
}

private Handler buildChain(GameObject gameObjectA) {
Handler handler = new CollisionCheckHandler();
if (gameObjectA.getId() % 2 == 0) {
handler.setNext(new DamageHandler());
}
return handler;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ru.otus.main_patterns.hw12.handler;

import ru.otus.main_patterns.hw12.GameObject;

public class CollisionCheckHandler extends Handler {

@Override
protected boolean process(GameObject a, GameObject b) {
int dx = a.getX() - b.getX();
int dy = a.getY() - b.getY();
int r = a.getSize() + b.getSize();
if (dx * dx + dy * dy <= r * r) {
System.out.println(
"\nCollisionCheckHandler, Collision detected: " + a.getId() + " & " + b.getId());
return true;
}
return false; // остановка цепочки
}
}
Loading
Loading