Commit 1ce0d184 authored by saviola's avatar saviola

Add CLI client, change kryonet version

- initial implementation of a CLI client for testing purposes
- change kryonet version to a more recent fork
- add shadowjar support and allow the client and server to be run
- fix some typos
- small refactoring for Java 9+
parent 1ad2e00d
......@@ -10,9 +10,8 @@ subprojects {
repositories {
mavenLocal()
maven {
url = 'https://repo.maven.apache.org/maven2'
}
maven { url "https://repo.maven.apache.org/maven2" }
maven { url 'https://jitpack.io' }
}
dependencies {
......
plugins {
id 'com.github.johnrengelman.shadow' version '5.2.0'
}
project.ext.mainClassName = "de.bitrain.hexx.client.controller.FrontController"
task run(dependsOn: classes, type: JavaExec) {
main = project.mainClassName
classpath = sourceSets.main.runtimeClasspath
standardInput = System.in
ignoreExitValue = true
}
shadowJar {
manifest {
attributes 'Main-Class': project.mainClassName
}
}
dependencies {
implementation project(':hexx-core')
implementation project(':hexx-network')
implementation project(':hexx-ai')
implementation 'com.googlecode.lanterna:lanterna:3.0.2'
}
package de.bitrain.hexx.client.controller;
import de.bitrain.hexx.client.controller.interfaces.ControllerI;
import de.bitrain.hexx.client.view.interfaces.ScreenI;
/**
* Abstract superclass of all controllers (except the FrontController).
*/
abstract public class AbstractController implements ControllerI
{
/**
* Calls init() upon controller instantiation.
*/
public AbstractController()
{
this.init();
}
/**
* Convenience method to get a controller from the front controller.
*/
@Override
public ControllerI getController(String controller)
{
return FrontController.getInstance().getController(controller);
}
/**
* Convenience method to get a screen from the front controller.
*/
@Override
public ScreenI getScreen(String view)
{
return FrontController.getInstance().getScreen(view);
}
/**
* Empty init implementation.
*/
@Override
public void init() {}
}
package de.bitrain.hexx.client.controller;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import de.bitrain.hexx.client.controller.interfaces.ControllerI;
import de.bitrain.hexx.client.view.interfaces.ScreenI;
/**
* This is the front controller for the text-based client.
*/
public class FrontController {
private static FrontController instance;
/**
* Here we collect all our controllers.
*/
private final HashMap<String, ControllerI> controllers;
/**
* Here we collect all our screens.
*/
private final HashMap<String, ScreenI> screens;
/**
* The main frame.
*/
private WindowBasedTextGUI gui;
private FrontController()
{
// Initialize HashMaps
this.controllers = new HashMap<>();
this.screens = new HashMap<>();
}
/**
* Singleton implementation.
*
* @return FrontController
*/
public static FrontController getInstance()
{
if (null == instance)
{
instance = new FrontController();
}
return instance;
}
/**
* Application entry point.
*/
public static void main(final String[] args) throws IOException {
com.esotericsoftware.minlog.Log.set(
com.esotericsoftware.minlog.Log.LEVEL_DEBUG);
// Create front controller and dispatch
FrontController.getInstance().dispatch();
}
/**
* This method starts the Application
*/
public void dispatch() throws IOException {
//try to load the game configuration file
// TODO close stream?
//final InputStream stream= this.getClass().getResourceAsStream("/configuration/config.xml");
//ApplicationConfig.GetInstance().Load(stream);
//load the resources of the application
//Resource.loadContent();
final GuiController guiController = (GuiController) this.getController("GuiController");
guiController.initializeGui();
this.gui = guiController.getGui();
((MenuController) this.getController("MenuController")).showMenu();
guiController.waitForGuiToClose();
}
/**
* Returns and if necessary first create controller.
*
* @param name Controller name.
* @return controller
*/
public ControllerI getController(final String name)
{
ControllerI controller;
if (this.controllers.containsKey(name))
{
controller = this.controllers.get(name);
}
else
{
try
{
final String className = "de.bitrain.hexx.client.controller." + name;
final Constructor<?>[] c = Class.forName(className)
.getDeclaredConstructors();
controller = (ControllerI) c[0].newInstance();
this.controllers.put(name, controller);
}
catch (final Exception e)
{
e.printStackTrace();
throw new RuntimeException("Invalid controller \"" + name
+ "\"");
}
}
return controller;
}
/**
* Returns a collection of all controllers.
*/
public Collection<ControllerI> getControllers()
{
return this.controllers.values();
}
public WindowBasedTextGUI getGui()
{
return this.gui;
}
/**
* Returns and if necessary first creates screen.
*
* @param name
* @return view
*/
public ScreenI getScreen(final String name)
{
ScreenI screen;
if (this.screens.containsKey(name))
{
screen = this.screens.get(name);
}
else
{
try
{
final String className = "de.bitrain.hexx.client.view.screen." + name;
final Constructor<?>[] c = Class.forName(className)
.getDeclaredConstructors();
screen = (ScreenI) c[0].newInstance();
this.screens.put(name, screen);
}
catch (final Exception e)
{
e.printStackTrace();
throw new RuntimeException("Invalid screen \"" + name + "\"");
}
}
return screen;
}
/**
* Returns a collection of all screens.
*/
public Collection<ScreenI> getScreens()
{
return this.screens.values();
}
}
package de.bitrain.hexx.client.controller;
import de.bitrain.hexx.client.view.screen.GameScreen;
import de.bitrain.hexx.core.logic.board.Board;
public class GameController extends AbstractController {
private GameScreen screen;
@Override
public void init() {
super.init();
this.screen = (GameScreen) this.getScreen("GameScreen");
}
public void showBoard() {
final Board board = new Board(4);
this.screen.openWindow();
this.screen.updateBoard(board);
((MenuController) this.getController("MenuController")).focusMenu();
}
}
package de.bitrain.hexx.client.controller;
import java.io.IOException;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import de.bitrain.hexx.client.view.screen.MainScreen;
import de.bitrain.hexx.client.view.screen.MenuScreen;
import de.bitrain.hexx.client.view.screen.SplashScreen;
public class GuiController extends AbstractController {
private WindowBasedTextGUI gui;
public WindowBasedTextGUI getGui() {
return gui;
}
public void initializeGui() {
this.gui = ((MainScreen) this.getScreen("MainScreen")).initializeGui();
try {
((SplashScreen) this.getScreen("SplashScreen")).drawBanner();
} catch (IOException e) {
e.printStackTrace();
}
}
public void waitForGuiToClose() {
((MenuScreen) this.getScreen("MenuScreen")).waitForMenuToClose();
((MainScreen) this.getScreen("MainScreen")).terminateGui();
}
}
package de.bitrain.hexx.client.controller;
import de.bitrain.hexx.client.view.screen.MenuScreen;
public class MenuController extends AbstractController {
private MenuScreen screen;
public void focusMenu() {
this.screen.setMenuActive();
}
@Override
public void init() {
super.init();
this.screen = ((MenuScreen) this.getScreen("MenuScreen"));
}
public void startSinglePlayerGame() {
// Connect to server, add local player, add AI opponent (optional), show board
}
public void startOnlineGame() {
this.startSinglePlayerGame();
}
public void showMenu() {
final GameController gameController = (GameController) this.getController("GameController");
this.screen.showMenu(this::startSinglePlayerGame, this::startOnlineGame);
}
}
package de.bitrain.hexx.client.controller.interfaces;
import de.bitrain.hexx.client.view.interfaces.ScreenI;
/**
* Interface implemented by all controllers.
*/
public interface ControllerI
{
/**
* Convenience method to get a controller.
*/
ControllerI getController(String controller);
/**
* Convenience method to get a screen.
*/
ScreenI getScreen(String view);
/**
* Initialization of the controller, will be called upon instantiation.
*/
void init();
}
package de.bitrain.hexx.client.view.interfaces;
public interface ScreenI {
}
package de.bitrain.hexx.client.view.screen;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import de.bitrain.hexx.client.controller.FrontController;
import de.bitrain.hexx.client.controller.GuiController;
import de.bitrain.hexx.client.view.interfaces.ScreenI;
public abstract class AbstractScreen implements ScreenI {
public WindowBasedTextGUI getGui() {
return ((GuiController) FrontController.getInstance().getController("GuiController")).getGui();
}
}
package de.bitrain.hexx.client.view.screen;
import java.util.Collections;
import java.util.Properties;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.graphics.PropertyTheme;
import com.googlecode.lanterna.graphics.Theme;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.Window;
import de.bitrain.hexx.core.logic.board.Board;
import de.bitrain.hexx.core.logic.board.Field;
public class GameScreen extends AbstractScreen {
private Theme outsideTheme, outsideBorderTheme, insideGapTheme, emptyFieldTheme;
private Theme[] playerThemes;
private Window window;
public void closeWindow() {
this.window.close();
}
/**
* Returns true if the game screen window is open and visible, false otherwise.
*/
public boolean isActive() {
return this.window != null && this.window.getTextGUI() != null;
}
public void openWindow() {
this.window = new BasicWindow();
this.window.setHints(Collections.singletonList(Window.Hint.CENTERED));
this.getGui().addWindow(this.window);
}
private void prepareThemes() {
final Properties outsideThemeProperties = new Properties();
outsideThemeProperties.put("foreground", TextColor.ANSI.GREEN);
outsideThemeProperties.put("background", TextColor.ANSI.BLACK);
final Properties outsideBorderThemeProperties = new Properties();
outsideBorderThemeProperties.put("foreground", TextColor.ANSI.GREEN);
outsideBorderThemeProperties.put("background", TextColor.ANSI.BLACK);
final Properties insideGapThemeProperties = new Properties();
insideGapThemeProperties.put("foreground", TextColor.ANSI.GREEN);
insideGapThemeProperties.put("background", TextColor.ANSI.BLACK);
final Properties emptyFieldThemeProperties = new Properties();
emptyFieldThemeProperties.put("foreground", TextColor.ANSI.GREEN);
emptyFieldThemeProperties.put("background", TextColor.ANSI.BLACK);
final Properties player1ThemeProperties = new Properties();
player1ThemeProperties.put("foreground", TextColor.ANSI.BLUE);
player1ThemeProperties.put("background", TextColor.ANSI.BLACK);
final Properties player2ThemeProperties = new Properties();
player2ThemeProperties.put("foreground", TextColor.ANSI.RED);
player2ThemeProperties.put("background", TextColor.ANSI.BLACK);
this.outsideTheme = new PropertyTheme(outsideThemeProperties);
this.outsideBorderTheme = new PropertyTheme(outsideBorderThemeProperties);
this.insideGapTheme = new PropertyTheme(insideGapThemeProperties);
this.emptyFieldTheme = new PropertyTheme(emptyFieldThemeProperties);
this.playerThemes = new Theme[] { new PropertyTheme(player1ThemeProperties),
new PropertyTheme(player2ThemeProperties) };
}
public boolean updateBoard(Board board) {
if (!this.isActive()) {
return false;
}
final Panel boardPanel = new Panel(new LinearLayout(Direction.VERTICAL));
final int[] positionMax = { 0, 0 };
board.getFields().forEach((field -> {
positionMax[0] = Math.max(field.getPosition().getX(), positionMax[0]);
positionMax[1] = Math.max(field.getPosition().getY(), positionMax[1]);
}));
Field[][] fields = new Field[positionMax[1] + 1][positionMax[0] + 1];
board.getFields().forEach((field ->
fields[field.getPosition().getY()][field.getPosition().getX()] = field));
for (Field[] fieldsRow : fields) {
final Panel rowPanel = new Panel(new LinearLayout(Direction.HORIZONTAL));
for (Field field : fieldsRow) {
Label fieldLabel = new Label(" ");
if (field == null) {
fieldLabel.setTheme(this.outsideTheme);
} else if (field.hasOwner()) {
fieldLabel.setText(String.valueOf(field.getBallCount()));
fieldLabel.setTheme(this.playerThemes[field.getOwner().getId()]);
} else {
fieldLabel.setText("0");
fieldLabel.setTheme(emptyFieldTheme);
}
rowPanel.addComponent(fieldLabel);
}
boardPanel.addComponent(rowPanel);
}
this.window.setComponent(boardPanel);
return true;
}
}
package de.bitrain.hexx.client.view.screen;
import java.io.IOException;
import java.util.Properties;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.graphics.PropertyTheme;
import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
public class MainScreen extends AbstractScreen {
public WindowBasedTextGUI initializeGui() {
DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory();
Screen screen = null;
try {
screen = terminalFactory.createScreen();
screen.startScreen();
final WindowBasedTextGUI textGui = new MultiWindowTextGUI(screen);
final Properties themeProperties = new Properties();
themeProperties.put("foreground", TextColor.ANSI.GREEN);
themeProperties.put("background", TextColor.ANSI.BLACK);
textGui.setTheme(new PropertyTheme(themeProperties));
return textGui;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void terminateGui() {
Screen screen = this.getGui().getScreen();
if(screen != null) {
try {
screen.stopScreen();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
}
package de.bitrain.hexx.client.view.screen;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Direction;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.Window;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
public class MenuScreen extends AbstractScreen {
private Window window;
public void setMenuActive() {
this.getGui().setActiveWindow(this.window);
}
public void showMenu(Runnable singlePlayerAction, Runnable playOnlineAction) {
final WindowBasedTextGUI textGui = this.getGui();
this.window = new BasicWindow("Menu");
Panel contentPanel = new Panel(new LinearLayout(Direction.HORIZONTAL));
Button buttonSinglePlayer = new Button("Single player", singlePlayerAction);
Button buttonPlayOnline = new Button("Play online", playOnlineAction);
Button buttonExit = new Button("Exit", this.window::close);
contentPanel.addComponent(buttonSinglePlayer);