Skip to content

Commit

Permalink
Merge pull request #20 from BlazingTwist/15-define-backend-communication
Browse files Browse the repository at this point in the history
Implement Backend/Frontend communication, refactoring and tests.
  • Loading branch information
BlazingTwist authored Dec 14, 2023
2 parents 7a24a06 + 2eb1b53 commit 4ed76ef
Show file tree
Hide file tree
Showing 145 changed files with 5,620 additions and 919 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ fabric.properties

target
/logs/
/src/main/jchess-web/api-docs
7 changes: 7 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 33 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
</execution>
</executions>
</plugin>

<!-- enables automatic execution of tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand All @@ -66,6 +67,25 @@
</properties>
</configuration>
</plugin>

<!-- Generierung der dx (DataExchange) Klassen -->
<plugin>
<groupId>org.jsonschema2pojo</groupId>
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<sourceDirectory>${basedir}/src/main/resources/dx/schema</sourceDirectory>
<targetPackage>dx</targetPackage>
<includeAdditionalProperties>false</includeAdditionalProperties>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down Expand Up @@ -97,24 +117,30 @@
<version>2.3.10.Final</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.1</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.10.0</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- testing report -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-reporting</artifactId>
<version>1.10.0</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/example/undertow/UndertowWebsocket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package example.undertow;

import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.PathHandler;
import io.undertow.server.handlers.resource.ClassPathResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.websockets.WebSocketConnectionCallback;
import io.undertow.websockets.core.AbstractReceiveListener;
import io.undertow.websockets.core.BufferedTextMessage;
import io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.core.WebSockets;
import io.undertow.websockets.spi.WebSocketHttpExchange;
import jakarta.servlet.ServletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class UndertowWebsocket {
private static final Logger logger = LoggerFactory.getLogger(UndertowWebsocket.class);

public static void main(String[] args) throws ServletException, IOException {
DeploymentInfo deployment = Servlets.deployment()
.setClassLoader(UndertowWebsocket.class.getClassLoader())
.setContextPath("")
.setDeploymentName("Example_UndertowWebsocket")
.setResourceManager(new ClassPathResourceManager(UndertowWebsocket.class.getClassLoader()));

DeploymentManager manager = Servlets.defaultContainer().addDeployment(deployment);
manager.deploy();
HttpHandler handler = manager.start();
PathHandler pathHandler = Handlers.path(handler)
.addPrefixPath("/websocket", Handlers.websocket(new SocketHandler()));

Undertow server = Undertow.builder()
.addHttpListener(8880, "localhost")
.setHandler(pathHandler)
.build();
server.start();
logger.info("Server started");
}

public static class SocketHandler extends AbstractReceiveListener implements WebSocketConnectionCallback {
private final List<WebSocketChannel> channels = new ArrayList<>();

@Override
public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) {
logger.info("onConnect");
this.channels.add(channel);

channel.getReceiveSetter().set(this);
channel.resumeReceives();
}

@Override
protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) {
String msgText = message.getData();
logger.info("Received message {}", msgText);
notifyChannels(msgText);
}

public void notifyChannels(String message) {
int numChannelsNotified = 0;
int numChannelsDropped = 0;
Iterator<WebSocketChannel> iterator = channels.iterator();
while (iterator.hasNext()) {
WebSocketChannel channel = iterator.next();
if (channel.getCloseCode() >= 0) {
iterator.remove();
numChannelsDropped++;
continue;
}

WebSockets.sendText(message, channel, null);
numChannelsNotified++;
}
logger.info("Notified {} channels, dropped {} channels", numChannelsNotified, numChannelsDropped);
}
}
}
36 changes: 1 addition & 35 deletions src/main/java/jchess/ecs/EcsEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,17 @@

import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.function.Consumer;

public class EcsEvent<TData> {
public static final int PRIORITY_HIGH = 100;
public static final int PRIORITY_NORMAL = 50;
public static final int PRIORITY_LOW = 0;
public abstract class EcsEvent<TData> {

private final TreeMap<Integer, List<ISystem<TData>>> systemsByPriority = new TreeMap<>(
(a, b) -> Integer.compare(b, a)
);
private final List<Consumer<TData>> preListeners = new ArrayList<>();
private final List<Consumer<TData>> postListeners = new ArrayList<>();
private final EntityManager entityManager;

public EcsEvent(EntityManager entityManager) {
this.entityManager = entityManager;
}

/**
* Registers a System with normal priority ({@link #PRIORITY_NORMAL})
*/
public void registerSystem(ISystem<TData> system) {
registerSystem(system, PRIORITY_NORMAL);
}

/**
* Registers a System with a given priority.
* @param system the System to register
* @param priority higher priority System execute first
*/
public void registerSystem(ISystem<TData> system, int priority) {
List<ISystem<TData>> systems = systemsByPriority.computeIfAbsent(priority, x -> new ArrayList<>());
systems.add(system);
}

public void fire(TData data) {
for (Consumer<TData> preListener : preListeners) {
preListener.accept(data);
}
for (List<ISystem<TData>> systems : systemsByPriority.values()) {
for (ISystem<TData> system : systems) {
entityManager.runSystem(system, data);
}
}
for (Consumer<TData> postListener : postListeners) {
postListener.accept(data);
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/jchess/ecs/EcsEventManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package jchess.ecs;

import java.util.HashMap;
import java.util.Map;

public class EcsEventManager {
private final Map<Class<?>, EcsEvent<?>> events = new HashMap<>();

public <T extends EcsEvent<?>> void registerEvent(T event) {
events.put(event.getClass(), event);
}

@SuppressWarnings("unchecked")
public <T extends EcsEvent<?>> T getEvent(Class<? extends EcsEvent<?>> eventClass) {
return (T) events.get(eventClass);
}
}
6 changes: 3 additions & 3 deletions src/main/java/jchess/ecs/Entity.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package jchess.ecs;

import jchess.game.common.marker.MarkerComponent;
import jchess.game.common.piece.PieceComponent;
import jchess.game.common.tile.TileComponent;
import jchess.game.common.components.MarkerComponent;
import jchess.game.common.components.PieceComponent;
import jchess.game.common.components.TileComponent;

import java.util.stream.Stream;

Expand Down
5 changes: 0 additions & 5 deletions src/main/java/jchess/ecs/EntityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,4 @@ public List<Entity> getEntities() {
return entities;
}

public <T> void runSystem(ISystem<T> system, T data) {
entities.stream()
.filter(system::acceptsEntity)
.forEach(entity -> system.update(entity, data));
}
}
6 changes: 0 additions & 6 deletions src/main/java/jchess/ecs/ISystem.java

This file was deleted.

119 changes: 119 additions & 0 deletions src/main/java/jchess/game/common/BaseChessGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package jchess.game.common;

import jchess.ecs.EcsEventManager;
import jchess.ecs.Entity;
import jchess.ecs.EntityManager;
import jchess.game.common.events.BoardClickedEvent;
import jchess.game.common.events.PieceMoveEvent;
import jchess.game.common.events.RenderEvent;
import jchess.game.common.components.MarkerComponent;
import jchess.game.common.components.MarkerType;
import jchess.game.common.theme.IIconKey;

public abstract class BaseChessGame implements IChessGame {
protected final EntityManager entityManager;
protected final EcsEventManager eventManager;
protected final int numPlayers;

protected int activePlayerId = 0;

public BaseChessGame(int numPlayers) {
this.entityManager = new EntityManager();
this.eventManager = new EcsEventManager();
this.numPlayers = numPlayers;

eventManager.registerEvent(new RenderEvent());
eventManager.registerEvent(new PieceMoveEvent());

BoardClickedEvent boardClickedEvent = new BoardClickedEvent();
eventManager.registerEvent(boardClickedEvent);
boardClickedEvent.addPostEventListener(vector -> onBoardClicked(vector.getX(), vector.getY()));
}

@Override
public abstract void start();

protected abstract Entity getEntityAtPosition(int x, int y);

protected abstract IIconKey getMarkerIcon(MarkerType markerType);

protected void onBoardClicked(int x, int y) {
Entity clickedEntity = getEntityAtPosition(x, y);
if (clickedEntity == null) {
return;
}

MarkerComponent clickedMarker = clickedEntity.marker;
// delete all markers
for (Entity entity : entityManager.getEntities()) {
entity.marker = null;
}

if (markerShouldConsumeClick(clickedMarker)) {
if (clickedMarker.onMarkerClicked != null) {
clickedMarker.onMarkerClicked.run();
}
} else if (clickedEntity.piece != null) {
// show the tiles this piece can move to
boolean isActivePiece = clickedEntity.piece.identifier.ownerId() == activePlayerId;
clickedEntity.findValidMoves().forEach(validMove -> createMoveMarker(clickedEntity, validMove, isActivePiece));
createSelectionMarker(clickedEntity);
} else if (clickedEntity.tile != null) {
// show which pieces can move to the selected tile
entityManager.getEntities().stream()
.filter(entity -> entity.piece != null
&& entity.tile != null
&& entity.findValidMoves().anyMatch(move -> move == clickedEntity))
.forEach(attacker -> {
createMoveMarker(clickedEntity, attacker, false);
});
createSelectionMarker(clickedEntity);
}

eventManager.getEvent(RenderEvent.class).fire(null);
}

protected boolean markerShouldConsumeClick(MarkerComponent marker) {
if (marker == null) return false;
if (marker.onMarkerClicked != null) return true;
if (marker.markerType == MarkerType.Selection) return true; // click consumed to hide all markers
return false;
}

protected void createSelectionMarker(Entity selectedTile) {
MarkerComponent marker = new MarkerComponent(this::getMarkerIcon);
marker.onMarkerClicked = null;
marker.markerType = MarkerType.Selection;
selectedTile.marker = marker;
}

protected void createMoveMarker(Entity fromTile, Entity toTile, boolean isActivePiece) {
MarkerComponent marker = new MarkerComponent(this::getMarkerIcon);
marker.onMarkerClicked = isActivePiece ? () -> movePiece(fromTile, toTile) : null;
marker.markerType = isActivePiece ? MarkerType.YesAction : MarkerType.NoAction;
toTile.marker = marker;
}

protected void movePiece(Entity fromTile, Entity toTile) {
toTile.piece = fromTile.piece;
fromTile.piece = null;

// end turn
activePlayerId = (activePlayerId + 1) % numPlayers;
}

@Override
public EntityManager getEntityManager() {
return entityManager;
}

@Override
public EcsEventManager getEventManager() {
return eventManager;
}

@Override
public int getActivePlayerId() {
return activePlayerId;
}
}
Loading

0 comments on commit 4ed76ef

Please sign in to comment.