diff --git a/client-api/pom.xml b/client-api/pom.xml index dd043f0..960ff5c 100644 --- a/client-api/pom.xml +++ b/client-api/pom.xml @@ -1,22 +1,22 @@ - - 4.0.0 - - - com.charego - lingo-websocket - 1.0 - - - lingo-websocket-client-api - Lingo WebSocket :: Client API - - - - ${project.groupId} - ${project.version} - lingo-websocket-common - - - - + + 4.0.0 + + + com.charego + lingo-websocket + 1.0 + + + lingo-websocket-client-api + Lingo WebSocket :: Client API + + + + ${project.groupId} + ${project.version} + lingo-websocket-common + + + + diff --git a/client-api/src/main/java/lingo/client/api/Destinations.java b/client-api/src/main/java/lingo/client/api/Destinations.java index a3d5594..eb1fad5 100644 --- a/client-api/src/main/java/lingo/client/api/Destinations.java +++ b/client-api/src/main/java/lingo/client/api/Destinations.java @@ -1,35 +1,35 @@ -package lingo.client.api; - -public class Destinations { - - public static final String CHAT = topicDestination("chat"); - - public static final String GAME_CLOSED = topicDestination("gameClosed"); - - public static final String GAME_HOSTED = topicDestination("gameHosted"); - - public static final String GAME_JOINED = topicDestination("gameJoined"); - - public static final String GAME_LEFT = topicDestination("gameLeft"); - - public static final String OPPONENT_JOINED = topicDestination("opponentJoined"); - - public static final String OPPONENT_REPORTS = topicDestination("opponentReports"); - - public static final String PLAYER_REPORTS = topicDestination("playerReports"); - - public static final String PRACTICE_GAME = topicDestination("practiceGame"); - - public static final String PRACTICE_REPORTS = topicDestination("practiceReports"); - - public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped"); - - public static final String SESSION_USERNAME = topicDestination("sessionUsername"); - - public static final String USER_JOINED = topicDestination("userJoined"); - - private static String topicDestination(String suffix) { - return "/topic/" + suffix; - } - -} +package lingo.client.api; + +public class Destinations { + + public static final String CHAT = topicDestination("chat"); + + public static final String GAME_CLOSED = topicDestination("gameClosed"); + + public static final String GAME_HOSTED = topicDestination("gameHosted"); + + public static final String GAME_JOINED = topicDestination("gameJoined"); + + public static final String GAME_LEFT = topicDestination("gameLeft"); + + public static final String OPPONENT_JOINED = topicDestination("opponentJoined"); + + public static final String OPPONENT_REPORTS = topicDestination("opponentReports"); + + public static final String PLAYER_REPORTS = topicDestination("playerReports"); + + public static final String PRACTICE_GAME = topicDestination("practiceGame"); + + public static final String PRACTICE_REPORTS = topicDestination("practiceReports"); + + public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped"); + + public static final String SESSION_USERNAME = topicDestination("sessionUsername"); + + public static final String USER_JOINED = topicDestination("userJoined"); + + private static String topicDestination(String suffix) { + return "/topic/" + suffix; + } + +} diff --git a/client/pom.xml b/client/pom.xml index 5253967..ed9d495 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -1,43 +1,43 @@ - - 4.0.0 - - - com.charego - lingo-websocket - 1.0 - - - lingo-websocket-client - Lingo WebSocket :: Client - - - - ${project.groupId} - ${project.version} - lingo-websocket-client-api - - - - - org.springframework.boot - spring-boot-starter-websocket - - - - - - - com.zenjava - javafx-maven-plugin - 8.8.3 - - lingo.client.bootstrap.LingoClient - src/main/config - true - - - - - - + + 4.0.0 + + + com.charego + lingo-websocket + 1.0 + + + lingo-websocket-client + Lingo WebSocket :: Client + + + + ${project.groupId} + ${project.version} + lingo-websocket-client-api + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + lingo.client.bootstrap.LingoClient + src/main/config + true + + + + + + diff --git a/client/src/main/config/application.yaml b/client/src/main/config/application.yaml index b6cf980..309b2fb 100644 --- a/client/src/main/config/application.yaml +++ b/client/src/main/config/application.yaml @@ -1,9 +1,9 @@ -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html - -# Development -web.base.url: http://localhost:8080 -web.socket.url: ws://localhost:8080/sockjs - -# Production -#web.base.url: http://lingo.charego.com -#web.socket.url: ws://lingo.charego.com/sockjs +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html + +# Development +web.base.url: http://localhost:8080 +web.socket.url: ws://localhost:8080/sockjs + +# Production +#web.base.url: http://lingo.charego.com +#web.socket.url: ws://lingo.charego.com/sockjs diff --git a/client/src/main/java/lingo/client/bootstrap/LingoClient.java b/client/src/main/java/lingo/client/bootstrap/LingoClient.java index b76db4f..f3e9459 100644 --- a/client/src/main/java/lingo/client/bootstrap/LingoClient.java +++ b/client/src/main/java/lingo/client/bootstrap/LingoClient.java @@ -1,65 +1,65 @@ -package lingo.client.bootstrap; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.scheduling.concurrent.CustomizableThreadFactory; - -import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.image.Image; -import javafx.stage.Stage; - -@SpringBootApplication -public class LingoClient extends Application { - - private Parent root; - - public static void main(final String[] args) { - Application.launch(args); - } - - @Override - public void init() throws Exception { - ConfigurableApplicationContext context = startSpringApplication(); - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Lingo.fxml")); - loader.setControllerFactory(clazz -> context.getBean(clazz)); - root = loader.load(); - } - - @Override - public void start(Stage stage) throws Exception { - // Close the Spring context when the client is closed. - stage.setOnCloseRequest(e -> { - stage.close(); - System.exit(0); - }); - Scene scene = new Scene(root); - scene.getStylesheets().add("/style.css"); - stage.getIcons().add(new Image(getClass().getResourceAsStream("/lingo.png"))); - stage.setResizable(false); - stage.setScene(scene); - stage.setTitle("Lingo"); - stage.show(); - } - - private ConfigurableApplicationContext startSpringApplication() { - SpringApplication application = new SpringApplication(LingoClient.class); - String[] args = getParameters().getRaw().stream().toArray(String[]::new); - application.setHeadless(false); - application.setWebEnvironment(false); - return application.run(args); - } - - @Bean - public ExecutorService executorService() { - return Executors.newFixedThreadPool(5, new CustomizableThreadFactory("ClientThread-")); - } - -} +package lingo.client.bootstrap; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.concurrent.CustomizableThreadFactory; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; + +@SpringBootApplication +public class LingoClient extends Application { + + private Parent root; + + public static void main(final String[] args) { + Application.launch(args); + } + + @Override + public void init() throws Exception { + ConfigurableApplicationContext context = startSpringApplication(); + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Lingo.fxml")); + loader.setControllerFactory(clazz -> context.getBean(clazz)); + root = loader.load(); + } + + @Override + public void start(Stage stage) throws Exception { + // Close the Spring context when the client is closed. + stage.setOnCloseRequest(e -> { + stage.close(); + System.exit(0); + }); + Scene scene = new Scene(root); + scene.getStylesheets().add("/style.css"); + stage.getIcons().add(new Image(getClass().getResourceAsStream("/lingo.png"))); + stage.setResizable(false); + stage.setScene(scene); + stage.setTitle("Lingo"); + stage.show(); + } + + private ConfigurableApplicationContext startSpringApplication() { + SpringApplication application = new SpringApplication(LingoClient.class); + String[] args = getParameters().getRaw().stream().toArray(String[]::new); + application.setHeadless(false); + application.setWebEnvironment(false); + return application.run(args); + } + + @Bean + public ExecutorService executorService() { + return Executors.newFixedThreadPool(5, new CustomizableThreadFactory("ClientThread-")); + } + +} diff --git a/client/src/main/java/lingo/client/bootstrap/LingoPresenter.java b/client/src/main/java/lingo/client/bootstrap/LingoPresenter.java index 4beef9e..8025f47 100644 --- a/client/src/main/java/lingo/client/bootstrap/LingoPresenter.java +++ b/client/src/main/java/lingo/client/bootstrap/LingoPresenter.java @@ -1,102 +1,102 @@ -package lingo.client.bootstrap; - -import java.io.IOException; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutorService; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.stereotype.Component; - -import javafx.application.Platform; -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.layout.BorderPane; -import javafx.stage.Stage; -import lingo.client.multiplayer.MultiplayerConfig; -import lingo.client.multiplayer.MultiplayerPresenter; -import lingo.client.singleplayer.SinglePlayerPresenter; -import lingo.client.util.FxmlController; -import lingo.common.WordReader; - -@Component -public class LingoPresenter implements FxmlController { - - private static final Logger log = LoggerFactory.getLogger(LingoPresenter.class); - - @Autowired - private ApplicationContext bootstrapContext; - - @Autowired - private ExecutorService executorService; - - @FXML - private BorderPane content; - - @FXML - private BorderPane gameModeChooser; - - @FXML - private void exit(ActionEvent event) { - Stage stage = (Stage) content.getScene().getWindow(); - stage.close(); - System.exit(0); - } - - @Override - public void initialize() { - // No initialization needed - } - - @FXML - private void showMultiplayer(ActionEvent event) { - log.info("Launching multiplayer..."); - - executorService.execute(() -> { - AnnotationConfigApplicationContext multiplayerContext = new AnnotationConfigApplicationContext(); - multiplayerContext.setParent(bootstrapContext); - multiplayerContext.scan(MultiplayerConfig.class.getPackage().getName()); - multiplayerContext.refresh(); - - MultiplayerPresenter presenter = multiplayerContext.getBean(MultiplayerPresenter.class); - presenter.setOnBackButtonPressed(e -> { - multiplayerContext.close(); - content.setCenter(gameModeChooser); - }); - - Platform.runLater(() -> { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LingoMultiplayer.fxml")); - loader.setControllerFactory(clazz -> multiplayerContext.getBean(clazz)); - try { - content.setCenter(loader.load()); - } catch (IOException e) { - log.error("Failed to load multiplayer", e); - } - }); - }); - } - - @FXML - private void showSinglePlayer(ActionEvent event) { - log.info("Launching single player..."); - - // TODO: Is there a memory leak here? - try { - Set guesses = WordReader.readFileToSet("/guesses.txt"); - List words = WordReader.readFileToList("/words.txt"); - SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> { - content.setCenter(gameModeChooser); - }); - content.setCenter(presenter.getNode()); - presenter.startGame(); - } catch (Exception e) { - log.error("Failed to load single player", e); - } - } - -} +package lingo.client.bootstrap; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.stereotype.Component; + +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import lingo.client.multiplayer.MultiplayerConfig; +import lingo.client.multiplayer.MultiplayerPresenter; +import lingo.client.singleplayer.SinglePlayerPresenter; +import lingo.client.util.FxmlController; +import lingo.common.WordReader; + +@Component +public class LingoPresenter implements FxmlController { + + private static final Logger log = LoggerFactory.getLogger(LingoPresenter.class); + + @Autowired + private ApplicationContext bootstrapContext; + + @Autowired + private ExecutorService executorService; + + @FXML + private BorderPane content; + + @FXML + private BorderPane gameModeChooser; + + @FXML + private void exit(ActionEvent event) { + Stage stage = (Stage) content.getScene().getWindow(); + stage.close(); + System.exit(0); + } + + @Override + public void initialize() { + // No initialization needed + } + + @FXML + private void showMultiplayer(ActionEvent event) { + log.info("Launching multiplayer..."); + + executorService.execute(() -> { + AnnotationConfigApplicationContext multiplayerContext = new AnnotationConfigApplicationContext(); + multiplayerContext.setParent(bootstrapContext); + multiplayerContext.scan(MultiplayerConfig.class.getPackage().getName()); + multiplayerContext.refresh(); + + MultiplayerPresenter presenter = multiplayerContext.getBean(MultiplayerPresenter.class); + presenter.setOnBackButtonPressed(e -> { + multiplayerContext.close(); + content.setCenter(gameModeChooser); + }); + + Platform.runLater(() -> { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LingoMultiplayer.fxml")); + loader.setControllerFactory(clazz -> multiplayerContext.getBean(clazz)); + try { + content.setCenter(loader.load()); + } catch (IOException e) { + log.error("Failed to load multiplayer", e); + } + }); + }); + } + + @FXML + private void showSinglePlayer(ActionEvent event) { + log.info("Launching single player..."); + + // TODO: Is there a memory leak here? + try { + Set guesses = WordReader.readFileToSet("/guesses.txt"); + List words = WordReader.readFileToList("/words.txt"); + SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> { + content.setCenter(gameModeChooser); + }); + content.setCenter(presenter.getNode()); + presenter.startGame(); + } catch (Exception e) { + log.error("Failed to load single player", e); + } + } + +} diff --git a/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java b/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java index 136f0b0..f0ecc29 100644 --- a/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java +++ b/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java @@ -1,60 +1,60 @@ -package lingo.client.multiplayer; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.web.client.RootUriTemplateHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.messaging.converter.ByteArrayMessageConverter; -import org.springframework.messaging.converter.CompositeMessageConverter; -import org.springframework.messaging.converter.MappingJackson2MessageConverter; -import org.springframework.messaging.converter.MessageConverter; -import org.springframework.messaging.converter.StringMessageConverter; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.socket.client.WebSocketClient; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.messaging.WebSocketStompClient; -import org.springframework.web.socket.sockjs.client.SockJsClient; -import org.springframework.web.socket.sockjs.client.Transport; -import org.springframework.web.socket.sockjs.client.WebSocketTransport; - -@Configuration -public class MultiplayerConfig { - - @Bean - public WebSocketClient webSocketClient() { - WebSocketClient webSocketClient = new StandardWebSocketClient(); - List transports = new ArrayList<>(); - transports.add(new WebSocketTransport(webSocketClient)); - SockJsClient sockJsClient = new SockJsClient(transports); - return sockJsClient; - } - - @Bean - public WebSocketStompClient stompClient(WebSocketClient webSocketClient, MessageConverter messageConverter) { - WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); - stompClient.setMessageConverter(messageConverter); - stompClient.setTaskScheduler(new ThreadPoolTaskScheduler()); - return stompClient; - } - - @Bean - public MessageConverter messageConverter() { - List converters = new ArrayList<>(); - converters.add(new StringMessageConverter()); - converters.add(new ByteArrayMessageConverter()); - converters.add(new MappingJackson2MessageConverter()); - return new CompositeMessageConverter(converters); - } - - @Bean - public RestTemplate restTemplate(Environment env) { - RestTemplate restTemplate = new RestTemplate(); - restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url"))); - return restTemplate; - } - -} +package lingo.client.multiplayer; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.web.client.RootUriTemplateHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.messaging.converter.ByteArrayMessageConverter; +import org.springframework.messaging.converter.CompositeMessageConverter; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.messaging.converter.StringMessageConverter; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.messaging.WebSocketStompClient; +import org.springframework.web.socket.sockjs.client.SockJsClient; +import org.springframework.web.socket.sockjs.client.Transport; +import org.springframework.web.socket.sockjs.client.WebSocketTransport; + +@Configuration +public class MultiplayerConfig { + + @Bean + public WebSocketClient webSocketClient() { + WebSocketClient webSocketClient = new StandardWebSocketClient(); + List transports = new ArrayList<>(); + transports.add(new WebSocketTransport(webSocketClient)); + SockJsClient sockJsClient = new SockJsClient(transports); + return sockJsClient; + } + + @Bean + public WebSocketStompClient stompClient(WebSocketClient webSocketClient, MessageConverter messageConverter) { + WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); + stompClient.setMessageConverter(messageConverter); + stompClient.setTaskScheduler(new ThreadPoolTaskScheduler()); + return stompClient; + } + + @Bean + public MessageConverter messageConverter() { + List converters = new ArrayList<>(); + converters.add(new StringMessageConverter()); + converters.add(new ByteArrayMessageConverter()); + converters.add(new MappingJackson2MessageConverter()); + return new CompositeMessageConverter(converters); + } + + @Bean + public RestTemplate restTemplate(Environment env) { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url"))); + return restTemplate; + } + +} diff --git a/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java b/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java index 9250796..55b2cc8 100644 --- a/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java +++ b/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java @@ -1,426 +1,426 @@ -package lingo.client.multiplayer; - -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.Collection; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; - -import javax.annotation.PostConstruct; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.messaging.simp.stomp.StompFrameHandler; -import org.springframework.messaging.simp.stomp.StompHeaders; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import javafx.application.Platform; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.geometry.VPos; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.control.Button; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.TextAlignment; -import javafx.scene.web.WebView; -import lingo.client.api.Destinations; -import lingo.client.util.FxmlController; -import lingo.client.view.Board; -import lingo.client.view.OpponentBoard; -import lingo.client.view.PlayerBoard; -import lingo.common.Game; -import lingo.common.GameLeftMessage; -import lingo.common.Report; - -@Component -public class MultiplayerPresenter implements FxmlController { - - private static final Logger log = LoggerFactory.getLogger(MultiplayerPresenter.class); - - private static final double MARGIN_BOTTOM = 75; - - @FXML - private Button backButton; - - @FXML - private Canvas canvas; - - @FXML - private StackPane contentPane; - - @FXML - private WebView webView; - - @Autowired - private ExecutorService executorService; - - @Autowired - private RestTemplate restTemplate; - - @Autowired - private StompTemplate stompTemplate; - - private EventHandler backButtonHandler; - - private GraphicsContext gc; - - private String lastWord; - - private PlayerBoard playerBoard; - - private OpponentBoard opponentBoard; - - private final CountDownLatch subscriptionsLatch = new CountDownLatch(7); - - private String username; - - private String opponentUsername; - - private void clearBoards(boolean clearScore) { - playerBoard.clearBoard(); - opponentBoard.clearBoard(); - if (clearScore) { - playerBoard.clearScore(); - opponentBoard.clearScore(); - } - } - - private void drawLastWord() { - if (lastWord != null) { - double x = canvas.getWidth() / 2; - double y = canvas.getHeight() - MARGIN_BOTTOM / 2; - gc.setFill(Color.BLACK); - gc.fillText("Previous word: " + lastWord.toUpperCase(), x, y); - } - } - - @Override - public void initialize() { - // Needed for key event handling - canvas.setFocusTraversable(true); - - gc = canvas.getGraphicsContext2D(); - gc.setFont(Font.font(24)); - gc.setTextAlign(TextAlignment.CENTER); - gc.setTextBaseline(VPos.CENTER); - playerBoard = new PlayerBoard(canvas, 50, 50); - opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50); - - Platform.runLater(() -> { - String html = getClass().getResource("/cube-grid.html").toExternalForm(); - String css = getClass().getResource("/cube-grid.css").toExternalForm(); - webView.getEngine().load(html); - webView.getEngine().setUserStyleSheetLocation(css); - webView.setContextMenuEnabled(false); - repaint(); - }); - - backButton.setOnAction(backButtonHandler); - - executorService.execute(() -> { - while (subscriptionsLatch.getCount() != 0) { - try { - subscriptionsLatch.await(); - } catch (InterruptedException ok) { - ok.printStackTrace(); - } - } - - username = UUID.randomUUID().toString().substring(0, 8); - stompTemplate.getSession().send("/app/setUsername", username); - - Collection games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody(); - boolean joinedGame = false; - for (Game game : games) { - if (game.getPlayerTwo() == null) { - stompTemplate.getSession().send("/app/joinGame", game.getId()); - joinedGame = true; - break; - } - } - if (!joinedGame) { - stompTemplate.getSession().send("/app/hostGame", null); - } - }); - } - - @FXML - private void keyPressed(KeyEvent e) { - final KeyCode keyCode = e.getCode(); - if (keyCode == KeyCode.BACK_SPACE) { - if (playerBoard.handleBackspace()) { - repaint(); - } - } else if (keyCode == KeyCode.ENTER) { - final String guess = playerBoard.handleEnter(); - if (guess != null) { - executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess)); - repaint(); - } - } else if (keyCode.isLetterKey()) { - if (playerBoard.handleLetter(keyCode.getName())) { - repaint(); - } - } - } - - private void newWord(String firstLetter) { - playerBoard.setProgress(0, firstLetter.charAt(0)); - } - - @PostConstruct - private void postConstruct() { - executorService.execute(() -> { - stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(), - subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(), - subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(), - subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(), - subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(), - subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(), - subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(), - subscription -> subscriptionsLatch.countDown()); - }); - } - - private void repaint() { - gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); - playerBoard.drawBoard(); - opponentBoard.drawBoard(); - drawLastWord(); - } - - public void setOnBackButtonPressed(EventHandler handler) { - backButtonHandler = handler; - } - - private void showWaitingAnimation(boolean show) { - if (show) { - contentPane.getChildren().add(webView); - backButton.toFront(); - } else { - contentPane.getChildren().remove(webView); - } - } - - private class GameClosedHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return Game.class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((Game) payload); - } - - private void handleMessage(Game game) { - log.debug("{} closed Game {}", game.getPlayerOne(), game.getId()); - } - } - - private class GameHostedHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return Game.class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((Game) payload); - } - - private void handleMessage(Game game) { - log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId()); - } - } - - private class GameJoinedHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return Game.class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((Game) payload); - } - - private void handleMessage(Game game) { - log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne()); - } - } - - private class GameLeftHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return GameLeftMessage.class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((GameLeftMessage) payload); - } - - private void handleMessage(GameLeftMessage message) { - final Game game = message.getGame(); - final String gameLeaver = message.getGameLeaver().getUsername(); - log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne()); - if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) { - Platform.runLater(() -> { - clearBoards(true); - showWaitingAnimation(true); - opponentUsername = null; - lastWord = null; - repaint(); - }); - } - } - } - - private class OpponentJoinedHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return String[].class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((String[]) payload); - } - - private void handleMessage(String[] message) { - final String firstLetter = message[0]; - opponentUsername = message[1]; - Platform.runLater(() -> { - clearBoards(true); - newWord(firstLetter); - showWaitingAnimation(false); - repaint(); - }); - } - } - - private class OpponentReportHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return Report.class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((Report) payload); - } - - private void handleMessage(Report report) { - if (report.isCorrect()) { - onCorrectGuess(report); - } else { - onIncorrectGuess(report); - } - } - - private void onCorrectGuess(Report report) { - final String guess = report.getGuess(); - final String firstLetter = report.getFirstLetter(); - log.info("Opponent guessed correctly: " + guess); - Platform.runLater(() -> { - opponentBoard.addToScore(100); - lastWord = guess; - clearBoards(false); - newWord(firstLetter); - repaint(); - }); - } - - private void onIncorrectGuess(Report report) { - final int[] result = report.getResult(); - log.info("Opponent result: " + Arrays.toString(result)); - Platform.runLater(() -> { - opponentBoard.addResult(result); - repaint(); - }); - } - } - - private class PlayerReportHandler implements StompFrameHandler { - - @Override - public Type getPayloadType(StompHeaders headers) { - return Report.class; - } - - @Override - public void handleFrame(StompHeaders headers, Object payload) { - handleMessage((Report) payload); - } - - private void handleMessage(Report report) { - if (report.isCorrect()) { - onCorrectGuess(report); - } else { - onIncorrectGuess(report); - } - } - - private void onCorrectGuess(Report report) { - final String guess = report.getGuess(); - final String firstLetter = report.getFirstLetter(); - log.info("I guessed correctly!"); - Platform.runLater(() -> { - playerBoard.addToScore(100); - lastWord = guess; - clearBoards(false); - newWord(firstLetter); - repaint(); - }); - } - - private void onIncorrectGuess(Report report) { - final String guess = report.getGuess(); - final int[] result = report.getResult(); - log.info("My result: " + Arrays.toString(result)); - Platform.runLater(() -> { - if (Game.isInvalid(result)) { - playerBoard.addGuess("-----"); - } else { - for (int i = 0; i < Game.WORD_LENGTH; i++) { - if (result[i] == Game.CORRECT_CHARACTER) { - playerBoard.setProgress(i, guess.charAt(i)); - } - } - playerBoard.addGuess(guess); - } - playerBoard.addResult(result); - repaint(); - }); - } - } - - private class GameList extends ParameterizedTypeReference> { - // intentionally left empty - } - -} +package lingo.client.multiplayer; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.messaging.simp.stomp.StompFrameHandler; +import org.springframework.messaging.simp.stomp.StompHeaders; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.geometry.VPos; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.Button; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.TextAlignment; +import javafx.scene.web.WebView; +import lingo.client.api.Destinations; +import lingo.client.util.FxmlController; +import lingo.client.view.Board; +import lingo.client.view.OpponentBoard; +import lingo.client.view.PlayerBoard; +import lingo.common.Game; +import lingo.common.GameLeftMessage; +import lingo.common.Report; + +@Component +public class MultiplayerPresenter implements FxmlController { + + private static final Logger log = LoggerFactory.getLogger(MultiplayerPresenter.class); + + private static final double MARGIN_BOTTOM = 75; + + @FXML + private Button backButton; + + @FXML + private Canvas canvas; + + @FXML + private StackPane contentPane; + + @FXML + private WebView webView; + + @Autowired + private ExecutorService executorService; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private StompTemplate stompTemplate; + + private EventHandler backButtonHandler; + + private GraphicsContext gc; + + private String lastWord; + + private PlayerBoard playerBoard; + + private OpponentBoard opponentBoard; + + private final CountDownLatch subscriptionsLatch = new CountDownLatch(7); + + private String username; + + private String opponentUsername; + + private void clearBoards(boolean clearScore) { + playerBoard.clearBoard(); + opponentBoard.clearBoard(); + if (clearScore) { + playerBoard.clearScore(); + opponentBoard.clearScore(); + } + } + + private void drawLastWord() { + if (lastWord != null) { + double x = canvas.getWidth() / 2; + double y = canvas.getHeight() - MARGIN_BOTTOM / 2; + gc.setFill(Color.BLACK); + gc.fillText("Previous word: " + lastWord.toUpperCase(), x, y); + } + } + + @Override + public void initialize() { + // Needed for key event handling + canvas.setFocusTraversable(true); + + gc = canvas.getGraphicsContext2D(); + gc.setFont(Font.font(24)); + gc.setTextAlign(TextAlignment.CENTER); + gc.setTextBaseline(VPos.CENTER); + playerBoard = new PlayerBoard(canvas, 50, 50); + opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50); + + Platform.runLater(() -> { + String html = getClass().getResource("/cube-grid.html").toExternalForm(); + String css = getClass().getResource("/cube-grid.css").toExternalForm(); + webView.getEngine().load(html); + webView.getEngine().setUserStyleSheetLocation(css); + webView.setContextMenuEnabled(false); + repaint(); + }); + + backButton.setOnAction(backButtonHandler); + + executorService.execute(() -> { + while (subscriptionsLatch.getCount() != 0) { + try { + subscriptionsLatch.await(); + } catch (InterruptedException ok) { + ok.printStackTrace(); + } + } + + username = UUID.randomUUID().toString().substring(0, 8); + stompTemplate.getSession().send("/app/setUsername", username); + + Collection games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody(); + boolean joinedGame = false; + for (Game game : games) { + if (game.getPlayerTwo() == null) { + stompTemplate.getSession().send("/app/joinGame", game.getId()); + joinedGame = true; + break; + } + } + if (!joinedGame) { + stompTemplate.getSession().send("/app/hostGame", null); + } + }); + } + + @FXML + private void keyPressed(KeyEvent e) { + final KeyCode keyCode = e.getCode(); + if (keyCode == KeyCode.BACK_SPACE) { + if (playerBoard.handleBackspace()) { + repaint(); + } + } else if (keyCode == KeyCode.ENTER) { + final String guess = playerBoard.handleEnter(); + if (guess != null) { + executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess)); + repaint(); + } + } else if (keyCode.isLetterKey()) { + if (playerBoard.handleLetter(keyCode.getName())) { + repaint(); + } + } + } + + private void newWord(String firstLetter) { + playerBoard.setProgress(0, firstLetter.charAt(0)); + } + + @PostConstruct + private void postConstruct() { + executorService.execute(() -> { + stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(), + subscription -> subscriptionsLatch.countDown()); + stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(), + subscription -> subscriptionsLatch.countDown()); + stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(), + subscription -> subscriptionsLatch.countDown()); + stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(), + subscription -> subscriptionsLatch.countDown()); + stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(), + subscription -> subscriptionsLatch.countDown()); + stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(), + subscription -> subscriptionsLatch.countDown()); + stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(), + subscription -> subscriptionsLatch.countDown()); + }); + } + + private void repaint() { + gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); + playerBoard.drawBoard(); + opponentBoard.drawBoard(); + drawLastWord(); + } + + public void setOnBackButtonPressed(EventHandler handler) { + backButtonHandler = handler; + } + + private void showWaitingAnimation(boolean show) { + if (show) { + contentPane.getChildren().add(webView); + backButton.toFront(); + } else { + contentPane.getChildren().remove(webView); + } + } + + private class GameClosedHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return Game.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((Game) payload); + } + + private void handleMessage(Game game) { + log.debug("{} closed Game {}", game.getPlayerOne(), game.getId()); + } + } + + private class GameHostedHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return Game.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((Game) payload); + } + + private void handleMessage(Game game) { + log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId()); + } + } + + private class GameJoinedHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return Game.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((Game) payload); + } + + private void handleMessage(Game game) { + log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne()); + } + } + + private class GameLeftHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return GameLeftMessage.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((GameLeftMessage) payload); + } + + private void handleMessage(GameLeftMessage message) { + final Game game = message.getGame(); + final String gameLeaver = message.getGameLeaver().getUsername(); + log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne()); + if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) { + Platform.runLater(() -> { + clearBoards(true); + showWaitingAnimation(true); + opponentUsername = null; + lastWord = null; + repaint(); + }); + } + } + } + + private class OpponentJoinedHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return String[].class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((String[]) payload); + } + + private void handleMessage(String[] message) { + final String firstLetter = message[0]; + opponentUsername = message[1]; + Platform.runLater(() -> { + clearBoards(true); + newWord(firstLetter); + showWaitingAnimation(false); + repaint(); + }); + } + } + + private class OpponentReportHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return Report.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((Report) payload); + } + + private void handleMessage(Report report) { + if (report.isCorrect()) { + onCorrectGuess(report); + } else { + onIncorrectGuess(report); + } + } + + private void onCorrectGuess(Report report) { + final String guess = report.getGuess(); + final String firstLetter = report.getFirstLetter(); + log.info("Opponent guessed correctly: " + guess); + Platform.runLater(() -> { + opponentBoard.addToScore(100); + lastWord = guess; + clearBoards(false); + newWord(firstLetter); + repaint(); + }); + } + + private void onIncorrectGuess(Report report) { + final int[] result = report.getResult(); + log.info("Opponent result: " + Arrays.toString(result)); + Platform.runLater(() -> { + opponentBoard.addResult(result); + repaint(); + }); + } + } + + private class PlayerReportHandler implements StompFrameHandler { + + @Override + public Type getPayloadType(StompHeaders headers) { + return Report.class; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + handleMessage((Report) payload); + } + + private void handleMessage(Report report) { + if (report.isCorrect()) { + onCorrectGuess(report); + } else { + onIncorrectGuess(report); + } + } + + private void onCorrectGuess(Report report) { + final String guess = report.getGuess(); + final String firstLetter = report.getFirstLetter(); + log.info("I guessed correctly!"); + Platform.runLater(() -> { + playerBoard.addToScore(100); + lastWord = guess; + clearBoards(false); + newWord(firstLetter); + repaint(); + }); + } + + private void onIncorrectGuess(Report report) { + final String guess = report.getGuess(); + final int[] result = report.getResult(); + log.info("My result: " + Arrays.toString(result)); + Platform.runLater(() -> { + if (Game.isInvalid(result)) { + playerBoard.addGuess("-----"); + } else { + for (int i = 0; i < Game.WORD_LENGTH; i++) { + if (result[i] == Game.CORRECT_CHARACTER) { + playerBoard.setProgress(i, guess.charAt(i)); + } + } + playerBoard.addGuess(guess); + } + playerBoard.addResult(result); + repaint(); + }); + } + } + + private class GameList extends ParameterizedTypeReference> { + // intentionally left empty + } + +} diff --git a/client/src/main/java/lingo/client/multiplayer/StompTemplate.java b/client/src/main/java/lingo/client/multiplayer/StompTemplate.java index d8d74c4..57821ff 100644 --- a/client/src/main/java/lingo/client/multiplayer/StompTemplate.java +++ b/client/src/main/java/lingo/client/multiplayer/StompTemplate.java @@ -1,126 +1,126 @@ -package lingo.client.multiplayer; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.function.Consumer; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.messaging.simp.stomp.StompFrameHandler; -import org.springframework.messaging.simp.stomp.StompHeaders; -import org.springframework.messaging.simp.stomp.StompSession; -import org.springframework.messaging.simp.stomp.StompSession.Subscription; -import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.messaging.WebSocketStompClient; - -@Component -public class StompTemplate { - - private static final Logger log = LoggerFactory.getLogger(StompTemplate.class); - - @Value("${web.socket.url}") - private String webSocketUrl; - - @Autowired - private ExecutorService executorService; - - @Autowired - private WebSocketStompClient stompClient; - - private StompSession stompSession; - - private final BlockingQueue subscriptionRequests = new LinkedBlockingQueue<>(); - - public StompSession getSession() { - /* - * TODO: If STOMP session is null or disconnected, create a new - * connection before returning this field. - */ - return stompSession; - } - - @PostConstruct - private void postConstruct() { - executorService.execute(() -> stompClient.connect(webSocketUrl, new WebSocketSessionHandler())); - new Thread(new WebSocketSessionListener()).start(); - } - - @PreDestroy - private void preDestroy() { - if (stompSession != null) { - log.info("Disconnecting from STOMP endpoint..."); - stompSession.disconnect(); - } - stompClient.stop(); - } - - public void subscribe(String destination, StompFrameHandler handler) { - subscribe(destination, handler, null); - } - - public void subscribe(String destination, StompFrameHandler handler, Consumer callback) { - try { - subscriptionRequests.put(new SubscriptionRequest(destination, handler, callback)); - } catch (InterruptedException e) { - log.error("Failed to subscribe to destination: {}", destination, e); - } - } - - private class SubscriptionRequest { - public final String destination; - public final StompFrameHandler handler; - public final Consumer callback; - - public SubscriptionRequest(String destination, StompFrameHandler handler, Consumer callback) { - this.destination = destination; - this.handler = handler; - this.callback = callback; - } - - public void onSubscribed(Subscription subscription) { - if (callback != null) { - callback.accept(subscription); - } - } - } - - private class WebSocketSessionHandler extends StompSessionHandlerAdapter { - @Override - public void afterConnected(StompSession session, StompHeaders connectedHeaders) { - log.info("Connected to STOMP endpoint"); - stompSession = session; - } - } - - private class WebSocketSessionListener implements Runnable { - - @Override - public void run() { - while (true) { - if (stompSession == null) { - try { - Thread.sleep(1000L); - } catch (InterruptedException ok) { - ok.printStackTrace(); - } - continue; - } - try { - final SubscriptionRequest request = subscriptionRequests.take(); - final Subscription subscription = stompSession.subscribe(request.destination, request.handler); - request.onSubscribed(subscription); - } catch (InterruptedException e) { - log.error("Failed to subscribe", e); - } - } - } - } - -} +package lingo.client.multiplayer; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Consumer; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.messaging.simp.stomp.StompFrameHandler; +import org.springframework.messaging.simp.stomp.StompHeaders; +import org.springframework.messaging.simp.stomp.StompSession; +import org.springframework.messaging.simp.stomp.StompSession.Subscription; +import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.WebSocketStompClient; + +@Component +public class StompTemplate { + + private static final Logger log = LoggerFactory.getLogger(StompTemplate.class); + + @Value("${web.socket.url}") + private String webSocketUrl; + + @Autowired + private ExecutorService executorService; + + @Autowired + private WebSocketStompClient stompClient; + + private StompSession stompSession; + + private final BlockingQueue subscriptionRequests = new LinkedBlockingQueue<>(); + + public StompSession getSession() { + /* + * TODO: If STOMP session is null or disconnected, create a new + * connection before returning this field. + */ + return stompSession; + } + + @PostConstruct + private void postConstruct() { + executorService.execute(() -> stompClient.connect(webSocketUrl, new WebSocketSessionHandler())); + new Thread(new WebSocketSessionListener()).start(); + } + + @PreDestroy + private void preDestroy() { + if (stompSession != null) { + log.info("Disconnecting from STOMP endpoint..."); + stompSession.disconnect(); + } + stompClient.stop(); + } + + public void subscribe(String destination, StompFrameHandler handler) { + subscribe(destination, handler, null); + } + + public void subscribe(String destination, StompFrameHandler handler, Consumer callback) { + try { + subscriptionRequests.put(new SubscriptionRequest(destination, handler, callback)); + } catch (InterruptedException e) { + log.error("Failed to subscribe to destination: {}", destination, e); + } + } + + private class SubscriptionRequest { + public final String destination; + public final StompFrameHandler handler; + public final Consumer callback; + + public SubscriptionRequest(String destination, StompFrameHandler handler, Consumer callback) { + this.destination = destination; + this.handler = handler; + this.callback = callback; + } + + public void onSubscribed(Subscription subscription) { + if (callback != null) { + callback.accept(subscription); + } + } + } + + private class WebSocketSessionHandler extends StompSessionHandlerAdapter { + @Override + public void afterConnected(StompSession session, StompHeaders connectedHeaders) { + log.info("Connected to STOMP endpoint"); + stompSession = session; + } + } + + private class WebSocketSessionListener implements Runnable { + + @Override + public void run() { + while (true) { + if (stompSession == null) { + try { + Thread.sleep(1000L); + } catch (InterruptedException ok) { + ok.printStackTrace(); + } + continue; + } + try { + final SubscriptionRequest request = subscriptionRequests.take(); + final Subscription subscription = stompSession.subscribe(request.destination, request.handler); + request.onSubscribed(subscription); + } catch (InterruptedException e) { + log.error("Failed to subscribe", e); + } + } + } + } + +} diff --git a/client/src/main/java/lingo/client/singleplayer/SinglePlayerPresenter.java b/client/src/main/java/lingo/client/singleplayer/SinglePlayerPresenter.java index b05ab7b..1a302d3 100644 --- a/client/src/main/java/lingo/client/singleplayer/SinglePlayerPresenter.java +++ b/client/src/main/java/lingo/client/singleplayer/SinglePlayerPresenter.java @@ -1,161 +1,161 @@ -package lingo.client.singleplayer; - -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javafx.application.Platform; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.geometry.VPos; -import javafx.scene.Node; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.control.Button; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.scene.text.Font; -import javafx.scene.text.TextAlignment; -import lingo.client.view.PlayerBoard; -import lingo.common.Game; -import lingo.common.Player; -import lingo.common.Report; - -public class SinglePlayerPresenter { - - private static final Logger log = LoggerFactory.getLogger(SinglePlayerPresenter.class); - - private final Button backButton; - - private final Canvas canvas; - - private final StackPane contentPane; - - private final GraphicsContext gc; - - private final PlayerBoard gameBoard; - - private final Game game; - - public SinglePlayerPresenter(List words, Set guesses, EventHandler backButtonHandler) { - backButton = new Button("Back"); - backButton.getStyleClass().add("game-nav"); - StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT); - StackPane.setMargin(backButton, new Insets(0, 0, 10, 10)); - backButton.setOnAction(backButtonHandler); - backButton.setPrefWidth(50); - - canvas = new Canvas(650, 420); - canvas.setFocusTraversable(true); - canvas.setOnKeyPressed(e -> keyPressed(e)); - gc = canvas.getGraphicsContext2D(); - gc.setFont(Font.font(24)); - gc.setTextAlign(TextAlignment.CENTER); - gc.setTextBaseline(VPos.CENTER); - - contentPane = new StackPane(); - contentPane.getChildren().add(canvas); - contentPane.getChildren().add(backButton); - - gameBoard = new PlayerBoard(canvas, 200, 60); - game = new Game(new Player("solo")); - game.setAcceptableGuesses(guesses); - game.setPossibleWords(words); - } - - private void clearBoards(boolean clearScore) { - gameBoard.clearBoard(); - if (clearScore) { - gameBoard.clearScore(); - } - } - - public Node getNode() { - return contentPane; - } - - private void keyPressed(KeyEvent e) { - final KeyCode keyCode = e.getCode(); - if (keyCode == KeyCode.BACK_SPACE) { - if (gameBoard.handleBackspace()) { - repaint(); - } - } else if (keyCode == KeyCode.ENTER) { - final String guess = gameBoard.handleEnter(); - if (guess != null) { - final int[] result = game.evaluate(guess); - Report report = new Report(); - report.setGuess(guess); - report.setResult(result); - if (Game.isCorrect(result)) { - final String newWord = game.newWord(); - final String firstLetter = String.valueOf(newWord.charAt(0)); - report.setCorrect(true); - report.setFirstLetter(firstLetter); - report.setResult(result); - onCorrectGuess(report); - } else { - onIncorrectGuess(report); - } - } - } else if (keyCode.isLetterKey()) { - if (gameBoard.handleLetter(keyCode.getName())) { - repaint(); - } - } - } - - private void newWord(String firstLetter) { - gameBoard.setProgress(0, firstLetter.charAt(0)); - } - - private void onCorrectGuess(Report report) { - final String firstLetter = report.getFirstLetter(); - log.info("I guessed correctly!"); - Platform.runLater(() -> { - gameBoard.addToScore(100); - clearBoards(false); - newWord(firstLetter); - repaint(); - }); - } - - private void onIncorrectGuess(Report report) { - final String guess = report.getGuess(); - final int[] result = report.getResult(); - log.info("My result: " + Arrays.toString(result)); - Platform.runLater(() -> { - if (Game.isInvalid(result)) { - gameBoard.addGuess("-----"); - } else { - for (int i = 0; i < Game.WORD_LENGTH; i++) { - if (result[i] == Game.CORRECT_CHARACTER) { - gameBoard.setProgress(i, guess.charAt(i)); - } - } - gameBoard.addGuess(guess); - } - gameBoard.addResult(result); - repaint(); - }); - } - - private void repaint() { - gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); - gameBoard.drawBoard(); - } - - public void startGame() { - final String firstWord = game.newGame(); - final String firstLetter = String.valueOf(firstWord.charAt(0)); - newWord(firstLetter); - repaint(); - } - -} +package lingo.client.singleplayer; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.Button; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Font; +import javafx.scene.text.TextAlignment; +import lingo.client.view.PlayerBoard; +import lingo.common.Game; +import lingo.common.Player; +import lingo.common.Report; + +public class SinglePlayerPresenter { + + private static final Logger log = LoggerFactory.getLogger(SinglePlayerPresenter.class); + + private final Button backButton; + + private final Canvas canvas; + + private final StackPane contentPane; + + private final GraphicsContext gc; + + private final PlayerBoard gameBoard; + + private final Game game; + + public SinglePlayerPresenter(List words, Set guesses, EventHandler backButtonHandler) { + backButton = new Button("Back"); + backButton.getStyleClass().add("game-nav"); + StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT); + StackPane.setMargin(backButton, new Insets(0, 0, 10, 10)); + backButton.setOnAction(backButtonHandler); + backButton.setPrefWidth(50); + + canvas = new Canvas(650, 420); + canvas.setFocusTraversable(true); + canvas.setOnKeyPressed(e -> keyPressed(e)); + gc = canvas.getGraphicsContext2D(); + gc.setFont(Font.font(24)); + gc.setTextAlign(TextAlignment.CENTER); + gc.setTextBaseline(VPos.CENTER); + + contentPane = new StackPane(); + contentPane.getChildren().add(canvas); + contentPane.getChildren().add(backButton); + + gameBoard = new PlayerBoard(canvas, 200, 60); + game = new Game(new Player("solo")); + game.setAcceptableGuesses(guesses); + game.setPossibleWords(words); + } + + private void clearBoards(boolean clearScore) { + gameBoard.clearBoard(); + if (clearScore) { + gameBoard.clearScore(); + } + } + + public Node getNode() { + return contentPane; + } + + private void keyPressed(KeyEvent e) { + final KeyCode keyCode = e.getCode(); + if (keyCode == KeyCode.BACK_SPACE) { + if (gameBoard.handleBackspace()) { + repaint(); + } + } else if (keyCode == KeyCode.ENTER) { + final String guess = gameBoard.handleEnter(); + if (guess != null) { + final int[] result = game.evaluate(guess); + Report report = new Report(); + report.setGuess(guess); + report.setResult(result); + if (Game.isCorrect(result)) { + final String newWord = game.newWord(); + final String firstLetter = String.valueOf(newWord.charAt(0)); + report.setCorrect(true); + report.setFirstLetter(firstLetter); + report.setResult(result); + onCorrectGuess(report); + } else { + onIncorrectGuess(report); + } + } + } else if (keyCode.isLetterKey()) { + if (gameBoard.handleLetter(keyCode.getName())) { + repaint(); + } + } + } + + private void newWord(String firstLetter) { + gameBoard.setProgress(0, firstLetter.charAt(0)); + } + + private void onCorrectGuess(Report report) { + final String firstLetter = report.getFirstLetter(); + log.info("I guessed correctly!"); + Platform.runLater(() -> { + gameBoard.addToScore(100); + clearBoards(false); + newWord(firstLetter); + repaint(); + }); + } + + private void onIncorrectGuess(Report report) { + final String guess = report.getGuess(); + final int[] result = report.getResult(); + log.info("My result: " + Arrays.toString(result)); + Platform.runLater(() -> { + if (Game.isInvalid(result)) { + gameBoard.addGuess("-----"); + } else { + for (int i = 0; i < Game.WORD_LENGTH; i++) { + if (result[i] == Game.CORRECT_CHARACTER) { + gameBoard.setProgress(i, guess.charAt(i)); + } + } + gameBoard.addGuess(guess); + } + gameBoard.addResult(result); + repaint(); + }); + } + + private void repaint() { + gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); + gameBoard.drawBoard(); + } + + public void startGame() { + final String firstWord = game.newGame(); + final String firstLetter = String.valueOf(firstWord.charAt(0)); + newWord(firstLetter); + repaint(); + } + +} diff --git a/client/src/main/java/lingo/client/util/FxmlController.java b/client/src/main/java/lingo/client/util/FxmlController.java index 6aa1d42..d180d64 100644 --- a/client/src/main/java/lingo/client/util/FxmlController.java +++ b/client/src/main/java/lingo/client/util/FxmlController.java @@ -1,25 +1,25 @@ -package lingo.client.util; - -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.fxml.Initializable; - -/** - * Identifies a controller that will be created by an {@link FXMLLoader}. The - * {@code FXMLLoader} will automatically inject {@code location} and - * {@code resources} properties into the controller, and then it will call the - * no-arg {@link #initialize()} method. This is the recommended approach: don't - * use the {@link Initializable} interface. - */ -public interface FxmlController { - - /** - * Called by the {@link FXMLLoader} to initialize a controller after its - * root element has been completely processed. This means all of the - * controller's {@link FXML} elements will be injected, and they can be used - * to wire up the GUI in ways that couldn't be accomplished using pure FXML, - * e.g. attaching property listeners. - */ - void initialize(); - -} +package lingo.client.util; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; + +/** + * Identifies a controller that will be created by an {@link FXMLLoader}. The + * {@code FXMLLoader} will automatically inject {@code location} and + * {@code resources} properties into the controller, and then it will call the + * no-arg {@link #initialize()} method. This is the recommended approach: don't + * use the {@link Initializable} interface. + */ +public interface FxmlController { + + /** + * Called by the {@link FXMLLoader} to initialize a controller after its + * root element has been completely processed. This means all of the + * controller's {@link FXML} elements will be injected, and they can be used + * to wire up the GUI in ways that couldn't be accomplished using pure FXML, + * e.g. attaching property listeners. + */ + void initialize(); + +} diff --git a/client/src/main/java/lingo/client/view/Board.java b/client/src/main/java/lingo/client/view/Board.java index fab7387..f1f2f0a 100644 --- a/client/src/main/java/lingo/client/view/Board.java +++ b/client/src/main/java/lingo/client/view/Board.java @@ -1,35 +1,35 @@ -package lingo.client.view; - -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; - -public abstract class Board { - - public static final double HEIGHT = 300; - public static final double WIDTH = 250; - public static final double SIDE = 50; - - protected final Canvas canvas; - - protected final GraphicsContext gc; - - /** The leftmost x-coordinate */ - protected final double xInit; - - /** The topmost y-coordinate */ - protected final double yInit; - - public Board(Canvas canvas, double xInit, double yInit) { - this.canvas = canvas; - this.gc = canvas.getGraphicsContext2D(); - this.xInit = xInit; - this.yInit = yInit; - } - - public void clearBoard() { - gc.clearRect(xInit, yInit, WIDTH, HEIGHT); - } - - public abstract void drawBoard(); - -} +package lingo.client.view; + +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; + +public abstract class Board { + + public static final double HEIGHT = 300; + public static final double WIDTH = 250; + public static final double SIDE = 50; + + protected final Canvas canvas; + + protected final GraphicsContext gc; + + /** The leftmost x-coordinate */ + protected final double xInit; + + /** The topmost y-coordinate */ + protected final double yInit; + + public Board(Canvas canvas, double xInit, double yInit) { + this.canvas = canvas; + this.gc = canvas.getGraphicsContext2D(); + this.xInit = xInit; + this.yInit = yInit; + } + + public void clearBoard() { + gc.clearRect(xInit, yInit, WIDTH, HEIGHT); + } + + public abstract void drawBoard(); + +} diff --git a/client/src/main/java/lingo/client/view/GameBoard.java b/client/src/main/java/lingo/client/view/GameBoard.java index c66a214..45ac501 100644 --- a/client/src/main/java/lingo/client/view/GameBoard.java +++ b/client/src/main/java/lingo/client/view/GameBoard.java @@ -1,80 +1,80 @@ -package lingo.client.view; - -import java.util.ArrayList; -import java.util.List; - -import javafx.scene.canvas.Canvas; -import javafx.scene.paint.Color; - -public abstract class GameBoard extends Board { - - /** Tracks the player's previous guess evaluations */ - protected final List results = new ArrayList<>(); - - /** Tracks the player's score */ - protected int score; - - public GameBoard(Canvas canvas, double xInit, double yInit) { - super(canvas, xInit, yInit); - } - - @Override - public void clearBoard() { - super.clearBoard(); - results.clear(); - } - - protected void drawScore() { - double scoreX = xInit + WIDTH / 2; - double scoreY = yInit / 2; - gc.setFill(Color.BLACK); - gc.fillText(String.valueOf(score), scoreX, scoreY); - } - - protected void drawGrid() { - gc.beginPath(); - for (int x = 0; x <= WIDTH; x += SIDE) { - gc.moveTo(xInit + x, yInit); - gc.lineTo(xInit + x, yInit + HEIGHT); - } - for (int y = 0; y <= HEIGHT; y += SIDE) { - gc.moveTo(xInit, yInit + y); - gc.lineTo(xInit + WIDTH, yInit + y); - } - gc.setFill(Color.BLACK); - gc.stroke(); - } - - protected void drawResults() { - double y = yInit + SIDE * 1.5; - int numResults = Math.min(4, results.size()); - for (int i = 0; i < numResults; i++) { - double x = xInit + SIDE * 0.5; - int[] result = results.get(results.size() - numResults + i); - for (int j = 0; j < 5; j++) { - if (result[j] == 1) { - gc.setFill(Color.YELLOW); - gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); - } else if (result[j] == 2) { - gc.setFill(Color.ORANGE); - gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); - } - x += SIDE; - } - y += SIDE; - } - } - - public void addResult(int[] value) { - results.add(value); - } - - public void addToScore(int value) { - score += value; - } - - public void clearScore() { - score = 0; - } - -} +package lingo.client.view; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.canvas.Canvas; +import javafx.scene.paint.Color; + +public abstract class GameBoard extends Board { + + /** Tracks the player's previous guess evaluations */ + protected final List results = new ArrayList<>(); + + /** Tracks the player's score */ + protected int score; + + public GameBoard(Canvas canvas, double xInit, double yInit) { + super(canvas, xInit, yInit); + } + + @Override + public void clearBoard() { + super.clearBoard(); + results.clear(); + } + + protected void drawScore() { + double scoreX = xInit + WIDTH / 2; + double scoreY = yInit / 2; + gc.setFill(Color.BLACK); + gc.fillText(String.valueOf(score), scoreX, scoreY); + } + + protected void drawGrid() { + gc.beginPath(); + for (int x = 0; x <= WIDTH; x += SIDE) { + gc.moveTo(xInit + x, yInit); + gc.lineTo(xInit + x, yInit + HEIGHT); + } + for (int y = 0; y <= HEIGHT; y += SIDE) { + gc.moveTo(xInit, yInit + y); + gc.lineTo(xInit + WIDTH, yInit + y); + } + gc.setFill(Color.BLACK); + gc.stroke(); + } + + protected void drawResults() { + double y = yInit + SIDE * 1.5; + int numResults = Math.min(4, results.size()); + for (int i = 0; i < numResults; i++) { + double x = xInit + SIDE * 0.5; + int[] result = results.get(results.size() - numResults + i); + for (int j = 0; j < 5; j++) { + if (result[j] == 1) { + gc.setFill(Color.YELLOW); + gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); + } else if (result[j] == 2) { + gc.setFill(Color.ORANGE); + gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); + } + x += SIDE; + } + y += SIDE; + } + } + + public void addResult(int[] value) { + results.add(value); + } + + public void addToScore(int value) { + score += value; + } + + public void clearScore() { + score = 0; + } + +} diff --git a/client/src/main/java/lingo/client/view/OpponentBoard.java b/client/src/main/java/lingo/client/view/OpponentBoard.java index 75cb233..c257f9c 100644 --- a/client/src/main/java/lingo/client/view/OpponentBoard.java +++ b/client/src/main/java/lingo/client/view/OpponentBoard.java @@ -1,18 +1,18 @@ -package lingo.client.view; - -import javafx.scene.canvas.Canvas; - -public class OpponentBoard extends GameBoard { - - public OpponentBoard(Canvas canvas, double xInit, double yInit) { - super(canvas, xInit, yInit); - } - - @Override - public void drawBoard() { - drawScore(); - drawResults(); - drawGrid(); - } - -} +package lingo.client.view; + +import javafx.scene.canvas.Canvas; + +public class OpponentBoard extends GameBoard { + + public OpponentBoard(Canvas canvas, double xInit, double yInit) { + super(canvas, xInit, yInit); + } + + @Override + public void drawBoard() { + drawScore(); + drawResults(); + drawGrid(); + } + +} diff --git a/client/src/main/java/lingo/client/view/PlayerBoard.java b/client/src/main/java/lingo/client/view/PlayerBoard.java index c48edb3..89690db 100644 --- a/client/src/main/java/lingo/client/view/PlayerBoard.java +++ b/client/src/main/java/lingo/client/view/PlayerBoard.java @@ -1,120 +1,120 @@ -package lingo.client.view; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javafx.scene.canvas.Canvas; -import javafx.scene.paint.Color; - -public class PlayerBoard extends GameBoard { - - /** Tracks the player's current guess */ - private final StringBuilder guess = new StringBuilder(); - - /** Tracks the player's previous guesses */ - private final List guesses = new ArrayList<>(); - - /** Tracks the player's progress toward the current word */ - private final Map progress = new HashMap<>(); - - public PlayerBoard(Canvas canvas, double xInit, double yInit) { - super(canvas, xInit, yInit); - } - - @Override - public void clearBoard() { - super.clearBoard(); - guess.setLength(0); - guesses.clear(); - progress.clear(); - results.clear(); - } - - @Override - public void drawBoard() { - drawScore(); - drawInput(); - drawResults(); - double yStart = drawGuesses(); - drawHint(yStart); - drawGrid(); - } - - private void drawInput() { - gc.setFill(Color.GREEN); - double x = xInit + SIDE * 0.5; - double y = yInit + SIDE * 0.5; - for (int i = 0; i < guess.length(); i++) { - String character = String.valueOf(guess.charAt(i)); - gc.fillText(character, x, y); - x += SIDE; - } - } - - private double drawGuesses() { - double y = yInit + SIDE * 1.5; - double numGuesses = Math.min(4, guesses.size()); - for (int i = 0; i < numGuesses; i++) { - double x = xInit + SIDE * 0.5; - String guess = guesses.get((int) (guesses.size() - numGuesses + i)); - for (int j = 0; j < 5; j++) { - String character = String.valueOf(guess.charAt(j)); - gc.setFill(Color.GREEN); - gc.fillText(character, x, y); - x += SIDE; - } - y += SIDE; - } - return y; - } - - private void drawHint(double yStart) { - double x = xInit + SIDE * 0.5; - for (int i = 0; i < 5; i++) { - if (progress.containsKey(i)) { - gc.fillText(progress.get(i), x, yStart); - } - x += SIDE; - } - } - - public void addGuess(String value) { - guesses.add(value); - } - - public String getGuess() { - return guess.toString(); - } - - public boolean handleBackspace() { - if (guess.length() > 0) { - guess.setLength(guess.length() - 1); - return true; - } - return false; - } - - public String handleEnter() { - if (guess.length() == 5) { - final String value = guess.toString(); - guess.setLength(0); - return value; - } - return null; - } - - public boolean handleLetter(String letter) { - if (guess.length() < 5) { - guess.append(letter); - return true; - } - return false; - } - - public void setProgress(int i, char letter) { - progress.put(i, String.valueOf(letter)); - } - -} +package lingo.client.view; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javafx.scene.canvas.Canvas; +import javafx.scene.paint.Color; + +public class PlayerBoard extends GameBoard { + + /** Tracks the player's current guess */ + private final StringBuilder guess = new StringBuilder(); + + /** Tracks the player's previous guesses */ + private final List guesses = new ArrayList<>(); + + /** Tracks the player's progress toward the current word */ + private final Map progress = new HashMap<>(); + + public PlayerBoard(Canvas canvas, double xInit, double yInit) { + super(canvas, xInit, yInit); + } + + @Override + public void clearBoard() { + super.clearBoard(); + guess.setLength(0); + guesses.clear(); + progress.clear(); + results.clear(); + } + + @Override + public void drawBoard() { + drawScore(); + drawInput(); + drawResults(); + double yStart = drawGuesses(); + drawHint(yStart); + drawGrid(); + } + + private void drawInput() { + gc.setFill(Color.GREEN); + double x = xInit + SIDE * 0.5; + double y = yInit + SIDE * 0.5; + for (int i = 0; i < guess.length(); i++) { + String character = String.valueOf(guess.charAt(i)); + gc.fillText(character, x, y); + x += SIDE; + } + } + + private double drawGuesses() { + double y = yInit + SIDE * 1.5; + double numGuesses = Math.min(4, guesses.size()); + for (int i = 0; i < numGuesses; i++) { + double x = xInit + SIDE * 0.5; + String guess = guesses.get((int) (guesses.size() - numGuesses + i)); + for (int j = 0; j < 5; j++) { + String character = String.valueOf(guess.charAt(j)); + gc.setFill(Color.GREEN); + gc.fillText(character, x, y); + x += SIDE; + } + y += SIDE; + } + return y; + } + + private void drawHint(double yStart) { + double x = xInit + SIDE * 0.5; + for (int i = 0; i < 5; i++) { + if (progress.containsKey(i)) { + gc.fillText(progress.get(i), x, yStart); + } + x += SIDE; + } + } + + public void addGuess(String value) { + guesses.add(value); + } + + public String getGuess() { + return guess.toString(); + } + + public boolean handleBackspace() { + if (guess.length() > 0) { + guess.setLength(guess.length() - 1); + return true; + } + return false; + } + + public String handleEnter() { + if (guess.length() == 5) { + final String value = guess.toString(); + guess.setLength(0); + return value; + } + return null; + } + + public boolean handleLetter(String letter) { + if (guess.length() < 5) { + guess.append(letter); + return true; + } + return false; + } + + public void setProgress(int i, char letter) { + progress.put(i, String.valueOf(letter)); + } + +} diff --git a/client/src/main/resources/cube-grid.css b/client/src/main/resources/cube-grid.css index eb9389d..1222ac6 100644 --- a/client/src/main/resources/cube-grid.css +++ b/client/src/main/resources/cube-grid.css @@ -1,195 +1,195 @@ -h1 { - font: 24pt sans-serif; - font-variant: small-caps; - text-align: center -} - -/* Below based on http://tobiasahlin.com/spinkit */ - -.sk-cube-grid { - width: 200px; - height: 200px; - margin: 35px auto; -} - -.sk-cube-grid .sk-cube { - width: 20%; - height: 20%; - background-color: orange; - float: left; - -webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; - animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; -} - -/* -0.0s - 26 -0.1s - 27, 21 -0.2s - 28, 22, 16 -0.3s - 29, 23, 17, 11 -0.4s - 30, 24, 18, 12, 06 -0.5s - 25, 19, 13, 07, 01 -0.6s - 20, 14, 08, 02 -0.7s - 15, 09, 03 -0.8s - 10, 04 -0.9s - 05 -*/ - -/* 0 second delay */ -.sk-cube-grid .sk-cube26 { - -webkit-animation-delay: 0s; - animation-delay: 0s; -} - -/* 0.1 second delay */ -.sk-cube-grid .sk-cube27 { - -webkit-animation-delay: 0.1s; - animation-delay: 0.1s; -} -.sk-cube-grid .sk-cube21 { - -webkit-animation-delay: 0.1s; - animation-delay: 0.1s; -} - -/* 0.2 second delay */ -.sk-cube-grid .sk-cube28 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; -} -.sk-cube-grid .sk-cube22 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; -} -.sk-cube-grid .sk-cube16 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; -} - -/* 0.3 second delay */ -.sk-cube-grid .sk-cube29 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} -.sk-cube-grid .sk-cube23 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} -.sk-cube-grid .sk-cube17 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} -.sk-cube-grid .sk-cube11 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} - -/* 0.4 second delay */ -.sk-cube-grid .sk-cube30 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube24 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube18 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube12 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube6 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} - -/* 0.5 second delay */ -.sk-cube-grid .sk-cube25 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube19 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube13 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube7 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube1 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} - -/* 0.6 second delay */ -.sk-cube-grid .sk-cube20 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} -.sk-cube-grid .sk-cube14 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} -.sk-cube-grid .sk-cube8 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} -.sk-cube-grid .sk-cube2 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} - -/* 0.7 second delay */ -.sk-cube-grid .sk-cube15 { - -webkit-animation-delay: 0.7s; - animation-delay: 0.7s; -} -.sk-cube-grid .sk-cube9 { - -webkit-animation-delay: 0.7s; - animation-delay: 0.7s; -} -.sk-cube-grid .sk-cube3 { - -webkit-animation-delay: 0.7s; - animation-delay: 0.7s; -} - -/* 0.8 second delay */ -.sk-cube-grid .sk-cube10 { - -webkit-animation-delay: 0.8s; - animation-delay: 0.8s; -} -.sk-cube-grid .sk-cube4 { - -webkit-animation-delay: 0.8s; - animation-delay: 0.8s; -} - -/* 0.9 second delay */ -.sk-cube-grid .sk-cube5 { - -webkit-animation-delay: 0.9s; - animation-delay: 0.9s; -} - -@-webkit-keyframes sk-cubeGridScaleDelay { - 0%, 70%, 100% { - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } 35% { - -webkit-transform: scale3D(0, 0, 1); - transform: scale3D(0, 0, 1); - } -} - -@keyframes sk-cubeGridScaleDelay { - 0%, 70%, 100% { - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } 35% { - -webkit-transform: scale3D(0, 0, 1); - transform: scale3D(0, 0, 1); - } -} +h1 { + font: 24pt sans-serif; + font-variant: small-caps; + text-align: center +} + +/* Below based on http://tobiasahlin.com/spinkit */ + +.sk-cube-grid { + width: 200px; + height: 200px; + margin: 35px auto; +} + +.sk-cube-grid .sk-cube { + width: 20%; + height: 20%; + background-color: orange; + float: left; + -webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; + animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; +} + +/* +0.0s - 26 +0.1s - 27, 21 +0.2s - 28, 22, 16 +0.3s - 29, 23, 17, 11 +0.4s - 30, 24, 18, 12, 06 +0.5s - 25, 19, 13, 07, 01 +0.6s - 20, 14, 08, 02 +0.7s - 15, 09, 03 +0.8s - 10, 04 +0.9s - 05 +*/ + +/* 0 second delay */ +.sk-cube-grid .sk-cube26 { + -webkit-animation-delay: 0s; + animation-delay: 0s; +} + +/* 0.1 second delay */ +.sk-cube-grid .sk-cube27 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} +.sk-cube-grid .sk-cube21 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} + +/* 0.2 second delay */ +.sk-cube-grid .sk-cube28 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.sk-cube-grid .sk-cube22 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.sk-cube-grid .sk-cube16 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +/* 0.3 second delay */ +.sk-cube-grid .sk-cube29 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube23 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube17 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube11 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} + +/* 0.4 second delay */ +.sk-cube-grid .sk-cube30 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube24 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube18 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube12 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube6 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +/* 0.5 second delay */ +.sk-cube-grid .sk-cube25 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube19 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube13 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube7 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube1 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} + +/* 0.6 second delay */ +.sk-cube-grid .sk-cube20 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube14 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube8 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube2 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +/* 0.7 second delay */ +.sk-cube-grid .sk-cube15 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} +.sk-cube-grid .sk-cube9 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} +.sk-cube-grid .sk-cube3 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} + +/* 0.8 second delay */ +.sk-cube-grid .sk-cube10 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} +.sk-cube-grid .sk-cube4 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +/* 0.9 second delay */ +.sk-cube-grid .sk-cube5 { + -webkit-animation-delay: 0.9s; + animation-delay: 0.9s; +} + +@-webkit-keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} + +@keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} diff --git a/client/src/main/resources/cube-grid.html b/client/src/main/resources/cube-grid.html index 232538e..b9b54a8 100644 --- a/client/src/main/resources/cube-grid.html +++ b/client/src/main/resources/cube-grid.html @@ -1,39 +1,39 @@ - - - -

Waiting for Opponent

- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - + + + +

Waiting for Opponent

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/client/src/main/resources/fxml/Lingo.fxml b/client/src/main/resources/fxml/Lingo.fxml index dcd3469..17afbd5 100644 --- a/client/src/main/resources/fxml/Lingo.fxml +++ b/client/src/main/resources/fxml/Lingo.fxml @@ -1,38 +1,38 @@ - - - - - - + + + + + + - - - -
- -
- - -
- - - -
-
- -
+ + + +
+ +
+ + +
+ + + +
+
+ +
diff --git a/client/src/main/resources/fxml/LingoMultiplayer.fxml b/client/src/main/resources/fxml/LingoMultiplayer.fxml index e81f3ba..5b8eb36 100644 --- a/client/src/main/resources/fxml/LingoMultiplayer.fxml +++ b/client/src/main/resources/fxml/LingoMultiplayer.fxml @@ -1,27 +1,27 @@ - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/client/src/main/resources/style.css b/client/src/main/resources/style.css index 857a159..0b283ad 100644 --- a/client/src/main/resources/style.css +++ b/client/src/main/resources/style.css @@ -1,12 +1,12 @@ -.text { - -fx-font-family: Helvetica; - -fx-font-smoothing-type: gray; -} - -.game-mode { - -fx-font-size: 24px; -} - -.game-nav { - -fx-font-size: 12px; -} +.text { + -fx-font-family: Helvetica; + -fx-font-smoothing-type: gray; +} + +.game-mode { + -fx-font-size: 24px; +} + +.game-nav { + -fx-font-size: 12px; +} diff --git a/common/pom.xml b/common/pom.xml index 2097ea7..adae62f 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -1,31 +1,31 @@ - - 4.0.0 - - - com.charego - lingo-websocket - 1.0 - - - lingo-websocket-common - Lingo WebSocket :: Common - - - - org.apache.commons - commons-lang3 - 3.3.2 - - - com.fasterxml.jackson.core - jackson-databind - - - junit - junit - test - - - - + + 4.0.0 + + + com.charego + lingo-websocket + 1.0 + + + lingo-websocket-common + Lingo WebSocket :: Common + + + + org.apache.commons + commons-lang3 + 3.3.2 + + + com.fasterxml.jackson.core + jackson-databind + + + junit + junit + test + + + + diff --git a/common/src/main/java/lingo/common/ChatMessage.java b/common/src/main/java/lingo/common/ChatMessage.java index 75c5276..a8525c5 100644 --- a/common/src/main/java/lingo/common/ChatMessage.java +++ b/common/src/main/java/lingo/common/ChatMessage.java @@ -1,32 +1,32 @@ -package lingo.common; - -public class ChatMessage { - - private String username; - - private String message; - - public ChatMessage() {} - - public ChatMessage(String username, String message) { - this.username = username; - this.message = message; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - -} +package lingo.common; + +public class ChatMessage { + + private String username; + + private String message; + + public ChatMessage() {} + + public ChatMessage(String username, String message) { + this.username = username; + this.message = message; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/common/src/main/java/lingo/common/Game.java b/common/src/main/java/lingo/common/Game.java index dca2edb..0339408 100644 --- a/common/src/main/java/lingo/common/Game.java +++ b/common/src/main/java/lingo/common/Game.java @@ -1,137 +1,137 @@ -package lingo.common; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - -public class Game { - - public static final int INCORRECT_CHARACTER = 0; - public static final int INCORRECT_POSITION = 1; - public static final int CORRECT_CHARACTER = 2; - public static final int WORD_LENGTH = 5; - - /** Nein nein nein nein nein! */ - private static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 }; - - private static final AtomicInteger idCounter = new AtomicInteger(0); - - private int id; - - private Player playerOne; - - private Player playerTwo; - - private Set acceptableGuesses; - - private List possibleWords; - - private String word; - - private int wordIndex = 0; - - public Game() { - // Empty constructor required for serialization - } - - public Game(Player host) { - this.id = idCounter.incrementAndGet(); - this.playerOne = host; - } - - private static int indexOf(char[] array, char searchTerm) { - for (int i = 0; i < WORD_LENGTH; i++) { - if (array[i] == searchTerm) { - return i; - } - } - return -1; - } - - public static boolean isCorrect(int[] result) { - for (int i = 0; i < WORD_LENGTH; i++) { - if (result[i] != CORRECT_CHARACTER) { - return false; - } - } - return true; - } - - public static boolean isInvalid(int[] result) { - return Arrays.equals(result, INVALID_GUESS); - } - - public int[] evaluate(String guess) { - if (!acceptableGuesses.contains(guess)) { - return INVALID_GUESS; - } - - // the guess is acceptable - int[] result = new int[WORD_LENGTH]; - char[] remaining = new char[WORD_LENGTH]; - for (int i = 0; i < WORD_LENGTH; i++) { - if (guess.charAt(i) == word.charAt(i)) { - result[i] = CORRECT_CHARACTER; - } else { - result[i] = INCORRECT_CHARACTER; - remaining[i] = word.charAt(i); - } - } - for (int i = 0; i < WORD_LENGTH; i++) { - if (result[i] == INCORRECT_CHARACTER) { - int index = indexOf(remaining, guess.charAt(i)); - if (index != -1) { - result[i] = INCORRECT_POSITION; - remaining[index] = 0; - } - } - } - return result; - } - - public int getId() { - return id; - } - - public Player getPlayerOne() { - return playerOne; - } - - public Player getPlayerTwo() { - return playerTwo; - } - - public String newGame() { - Collections.shuffle(possibleWords); - wordIndex = 0; - return newWord(); - } - - public String newWord() { - word = possibleWords.get(wordIndex++); - return word; - } - - public void setAcceptableGuesses(Set value) { - this.acceptableGuesses = value; - } - - public void setId(int value) { - this.id = value; - } - - public void setPlayerOne(Player value) { - this.playerOne = value; - } - - public void setPlayerTwo(Player value) { - this.playerTwo = value; - } - - public void setPossibleWords(List value) { - this.possibleWords = value; - } - -} +package lingo.common; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +public class Game { + + public static final int INCORRECT_CHARACTER = 0; + public static final int INCORRECT_POSITION = 1; + public static final int CORRECT_CHARACTER = 2; + public static final int WORD_LENGTH = 5; + + /** Nein nein nein nein nein! */ + private static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 }; + + private static final AtomicInteger idCounter = new AtomicInteger(0); + + private int id; + + private Player playerOne; + + private Player playerTwo; + + private Set acceptableGuesses; + + private List possibleWords; + + private String word; + + private int wordIndex = 0; + + public Game() { + // Empty constructor required for serialization + } + + public Game(Player host) { + this.id = idCounter.incrementAndGet(); + this.playerOne = host; + } + + private static int indexOf(char[] array, char searchTerm) { + for (int i = 0; i < WORD_LENGTH; i++) { + if (array[i] == searchTerm) { + return i; + } + } + return -1; + } + + public static boolean isCorrect(int[] result) { + for (int i = 0; i < WORD_LENGTH; i++) { + if (result[i] != CORRECT_CHARACTER) { + return false; + } + } + return true; + } + + public static boolean isInvalid(int[] result) { + return Arrays.equals(result, INVALID_GUESS); + } + + public int[] evaluate(String guess) { + if (!acceptableGuesses.contains(guess)) { + return INVALID_GUESS; + } + + // the guess is acceptable + int[] result = new int[WORD_LENGTH]; + char[] remaining = new char[WORD_LENGTH]; + for (int i = 0; i < WORD_LENGTH; i++) { + if (guess.charAt(i) == word.charAt(i)) { + result[i] = CORRECT_CHARACTER; + } else { + result[i] = INCORRECT_CHARACTER; + remaining[i] = word.charAt(i); + } + } + for (int i = 0; i < WORD_LENGTH; i++) { + if (result[i] == INCORRECT_CHARACTER) { + int index = indexOf(remaining, guess.charAt(i)); + if (index != -1) { + result[i] = INCORRECT_POSITION; + remaining[index] = 0; + } + } + } + return result; + } + + public int getId() { + return id; + } + + public Player getPlayerOne() { + return playerOne; + } + + public Player getPlayerTwo() { + return playerTwo; + } + + public String newGame() { + Collections.shuffle(possibleWords); + wordIndex = 0; + return newWord(); + } + + public String newWord() { + word = possibleWords.get(wordIndex++); + return word; + } + + public void setAcceptableGuesses(Set value) { + this.acceptableGuesses = value; + } + + public void setId(int value) { + this.id = value; + } + + public void setPlayerOne(Player value) { + this.playerOne = value; + } + + public void setPlayerTwo(Player value) { + this.playerTwo = value; + } + + public void setPossibleWords(List value) { + this.possibleWords = value; + } + +} diff --git a/common/src/main/java/lingo/common/GameLeftMessage.java b/common/src/main/java/lingo/common/GameLeftMessage.java index 989e1d0..b15296f 100644 --- a/common/src/main/java/lingo/common/GameLeftMessage.java +++ b/common/src/main/java/lingo/common/GameLeftMessage.java @@ -1,32 +1,32 @@ -package lingo.common; - -public class GameLeftMessage { - - private Game game; - - private Player gameLeaver; - - public GameLeftMessage() {} - - public GameLeftMessage(Game game, Player gameLeaver) { - this.game = game; - this.gameLeaver = gameLeaver; - } - - public Game getGame() { - return game; - } - - public Player getGameLeaver() { - return gameLeaver; - } - - public void setGame(Game game) { - this.game = game; - } - - public void setGameLeaver(Player gameLeaver) { - this.gameLeaver = gameLeaver; - } - -} +package lingo.common; + +public class GameLeftMessage { + + private Game game; + + private Player gameLeaver; + + public GameLeftMessage() {} + + public GameLeftMessage(Game game, Player gameLeaver) { + this.game = game; + this.gameLeaver = gameLeaver; + } + + public Game getGame() { + return game; + } + + public Player getGameLeaver() { + return gameLeaver; + } + + public void setGame(Game game) { + this.game = game; + } + + public void setGameLeaver(Player gameLeaver) { + this.gameLeaver = gameLeaver; + } + +} diff --git a/common/src/main/java/lingo/common/Player.java b/common/src/main/java/lingo/common/Player.java index 077620b..79a3fb2 100644 --- a/common/src/main/java/lingo/common/Player.java +++ b/common/src/main/java/lingo/common/Player.java @@ -1,40 +1,40 @@ -package lingo.common; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -public class Player { - - @JsonIgnore - private String sessionId; - - private String username; - - public Player() { - // Empty constructor required for serialization - } - - public Player(String sessionId) { - this.sessionId = sessionId; - } - - public String getSessionId() { - return sessionId; - } - - public String getUsername() { - return username; - } - - public void setUsername(String value) { - this.username = value; - } - - @Override - public String toString() { - if (username != null) { - return username; - } - return sessionId; - } - -} +package lingo.common; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class Player { + + @JsonIgnore + private String sessionId; + + private String username; + + public Player() { + // Empty constructor required for serialization + } + + public Player(String sessionId) { + this.sessionId = sessionId; + } + + public String getSessionId() { + return sessionId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String value) { + this.username = value; + } + + @Override + public String toString() { + if (username != null) { + return username; + } + return sessionId; + } + +} diff --git a/common/src/main/java/lingo/common/Report.java b/common/src/main/java/lingo/common/Report.java index 2334d2c..c1e5d98 100644 --- a/common/src/main/java/lingo/common/Report.java +++ b/common/src/main/java/lingo/common/Report.java @@ -1,47 +1,47 @@ -package lingo.common; - -public class Report { - - private boolean correct; - - private String firstLetter; - - private String guess; - - private int[] result; - - public Report() {} - - public String getFirstLetter() { - return firstLetter; - } - - public void setFirstLetter(String value) { - this.firstLetter = value; - } - - public String getGuess() { - return guess; - } - - public void setGuess(String value) { - this.guess = value; - } - - public int[] getResult() { - return result; - } - - public void setResult(int[] value) { - this.result = value; - } - - public boolean isCorrect() { - return correct; - } - - public void setCorrect(boolean value) { - this.correct = value; - } - -} +package lingo.common; + +public class Report { + + private boolean correct; + + private String firstLetter; + + private String guess; + + private int[] result; + + public Report() {} + + public String getFirstLetter() { + return firstLetter; + } + + public void setFirstLetter(String value) { + this.firstLetter = value; + } + + public String getGuess() { + return guess; + } + + public void setGuess(String value) { + this.guess = value; + } + + public int[] getResult() { + return result; + } + + public void setResult(int[] value) { + this.result = value; + } + + public boolean isCorrect() { + return correct; + } + + public void setCorrect(boolean value) { + this.correct = value; + } + +} diff --git a/common/src/main/java/lingo/common/SetUsernameMessage.java b/common/src/main/java/lingo/common/SetUsernameMessage.java index fab52f4..73515a1 100644 --- a/common/src/main/java/lingo/common/SetUsernameMessage.java +++ b/common/src/main/java/lingo/common/SetUsernameMessage.java @@ -1,43 +1,43 @@ -package lingo.common; - -public class SetUsernameMessage { - - private String errorMessage; - - private boolean success; - - private String username; - - public SetUsernameMessage() {} - - public SetUsernameMessage(boolean success, String username, String errorMessage) { - this.errorMessage = errorMessage; - this.success = success; - this.username = username; - } - - public String getErrorMessage() { - return errorMessage; - } - - public boolean isSuccess() { - return success; - } - - public String getUsername() { - return username; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - public void setSuccess(boolean success) { - this.success = success; - } - - public void setUsername(String username) { - this.username = username; - } - -} +package lingo.common; + +public class SetUsernameMessage { + + private String errorMessage; + + private boolean success; + + private String username; + + public SetUsernameMessage() {} + + public SetUsernameMessage(boolean success, String username, String errorMessage) { + this.errorMessage = errorMessage; + this.success = success; + this.username = username; + } + + public String getErrorMessage() { + return errorMessage; + } + + public boolean isSuccess() { + return success; + } + + public String getUsername() { + return username; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public void setUsername(String username) { + this.username = username; + } + +} diff --git a/common/src/main/java/lingo/common/WordReader.java b/common/src/main/java/lingo/common/WordReader.java index aa574fa..0868357 100644 --- a/common/src/main/java/lingo/common/WordReader.java +++ b/common/src/main/java/lingo/common/WordReader.java @@ -1,38 +1,38 @@ -package lingo.common; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class WordReader { - - private static void readFileToCollection(String filename, Collection c) throws IOException { - try (final InputStream stream = WordReader.class.getResourceAsStream(filename); - final InputStreamReader streamReader = new InputStreamReader(stream); - final BufferedReader bufferedReader = new BufferedReader(streamReader)) { - String line = null; - while ((line = bufferedReader.readLine()) != null) { - c.add(line); - } - } - } - - public static List readFileToList(String filename) throws IOException { - final List list = new ArrayList<>(); - readFileToCollection(filename, list); - return list; - } - - public static Set readFileToSet(String filename) throws IOException { - final Set list = new HashSet<>(); - readFileToCollection(filename, list); - return list; - } - -} +package lingo.common; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class WordReader { + + private static void readFileToCollection(String filename, Collection c) throws IOException { + try (final InputStream stream = WordReader.class.getResourceAsStream(filename); + final InputStreamReader streamReader = new InputStreamReader(stream); + final BufferedReader bufferedReader = new BufferedReader(streamReader)) { + String line = null; + while ((line = bufferedReader.readLine()) != null) { + c.add(line); + } + } + } + + public static List readFileToList(String filename) throws IOException { + final List list = new ArrayList<>(); + readFileToCollection(filename, list); + return list; + } + + public static Set readFileToSet(String filename) throws IOException { + final Set list = new HashSet<>(); + readFileToCollection(filename, list); + return list; + } + +} diff --git a/pom.xml b/pom.xml index 8f2d4de..d241d47 100644 --- a/pom.xml +++ b/pom.xml @@ -1,40 +1,40 @@ - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 1.5.10.RELEASE - - - com.charego - lingo-websocket - 1.0 - pom - - Lingo WebSocket - - - UTF-8 - 1.8 - - - - client-api - common - server - - - - - - - javafx - - client - - - - - + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 1.5.10.RELEASE + + + com.charego + lingo-websocket + 1.0 + pom + + Lingo WebSocket + + + UTF-8 + 1.8 + + + + client-api + common + server + + + + + + + javafx + + client + + + + + diff --git a/server/pom.xml b/server/pom.xml index a4becfb..47c6170 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -1,64 +1,64 @@ - - 4.0.0 - - - com.charego - lingo-websocket - 1.0 - - - lingo-websocket-server - Lingo WebSocket :: Server - - - - ${project.groupId} - ${project.version} - lingo-websocket-client-api - - - - - org.springframework.boot - spring-boot-starter-websocket - - - - - org.springframework.boot - spring-boot-devtools - - true - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - true - - src/main/config - - - - - - repackage - - - ${project.artifactId} - - - - - - - - + + 4.0.0 + + + com.charego + lingo-websocket + 1.0 + + + lingo-websocket-server + Lingo WebSocket :: Server + + + + ${project.groupId} + ${project.version} + lingo-websocket-client-api + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + org.springframework.boot + spring-boot-devtools + + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + true + + src/main/config + + + + + + repackage + + + ${project.artifactId} + + + + + + + + diff --git a/server/src/main/java/lingo/server/LingoServer.java b/server/src/main/java/lingo/server/LingoServer.java index 4337da0..0dd8f77 100644 --- a/server/src/main/java/lingo/server/LingoServer.java +++ b/server/src/main/java/lingo/server/LingoServer.java @@ -1,20 +1,20 @@ -package lingo.server; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class LingoServer { - - public static void main(String[] args) { - - // Heroku dynamically assigns a port - final String webPort = System.getenv("PORT"); - if (webPort != null && !webPort.isEmpty()) { - System.setProperty("server.port", webPort); - } - - SpringApplication.run(LingoServer.class, args); - } - -} +package lingo.server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LingoServer { + + public static void main(String[] args) { + + // Heroku dynamically assigns a port + final String webPort = System.getenv("PORT"); + if (webPort != null && !webPort.isEmpty()) { + System.setProperty("server.port", webPort); + } + + SpringApplication.run(LingoServer.class, args); + } + +} diff --git a/server/src/main/java/lingo/server/SessionManager.java b/server/src/main/java/lingo/server/SessionManager.java index 7e6b41e..de0bc83 100644 --- a/server/src/main/java/lingo/server/SessionManager.java +++ b/server/src/main/java/lingo/server/SessionManager.java @@ -1,85 +1,85 @@ -package lingo.server; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationListener; -import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.messaging.AbstractSubProtocolEvent; -import org.springframework.web.socket.messaging.SessionConnectedEvent; -import org.springframework.web.socket.messaging.SessionDisconnectEvent; - -import lingo.common.Player; - -@Component -public class SessionManager implements ApplicationListener { - - public interface Listener { - void playerJoined(Player player); - void playerLeft(Player player); - } - - private static final Logger log = LoggerFactory.getLogger(SessionManager.class); - - private final Map playerBySession = new HashMap<>(); - - private final Set listeners = new HashSet<>(); - - public void addListener(Listener listener) { - synchronized (listeners) { - listeners.add(listener); - } - } - - public Player getPlayer(String sessionId) { - return playerBySession.get(sessionId); - } - - public int getPlayerCount() { - return playerBySession.size(); - } - - @Override - public void onApplicationEvent(AbstractSubProtocolEvent event) { - if (event instanceof SessionConnectedEvent) { - onSessionConnected((SessionConnectedEvent) event); - } else if (event instanceof SessionDisconnectEvent) { - onSessionDisconnect((SessionDisconnectEvent) event); - } - } - - private void onSessionConnected(SessionConnectedEvent event) { - final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId(); - final Player player = new Player(sessionId); - log.info("Player connected: {}", player); - playerBySession.put(sessionId, player); - synchronized (listeners) { - for (Listener listener : listeners) { - listener.playerJoined(player); - } - } - } - - private void onSessionDisconnect(SessionDisconnectEvent event) { - final String sessionId = event.getSessionId(); - final Player player = playerBySession.remove(sessionId); - log.info("Player disconnected: {}", player); - synchronized (listeners) { - for (Listener listener : listeners) { - listener.playerLeft(player); - } - } - } - - public void removeListener(Listener listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - -} +package lingo.server; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.AbstractSubProtocolEvent; +import org.springframework.web.socket.messaging.SessionConnectedEvent; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +import lingo.common.Player; + +@Component +public class SessionManager implements ApplicationListener { + + public interface Listener { + void playerJoined(Player player); + void playerLeft(Player player); + } + + private static final Logger log = LoggerFactory.getLogger(SessionManager.class); + + private final Map playerBySession = new HashMap<>(); + + private final Set listeners = new HashSet<>(); + + public void addListener(Listener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public Player getPlayer(String sessionId) { + return playerBySession.get(sessionId); + } + + public int getPlayerCount() { + return playerBySession.size(); + } + + @Override + public void onApplicationEvent(AbstractSubProtocolEvent event) { + if (event instanceof SessionConnectedEvent) { + onSessionConnected((SessionConnectedEvent) event); + } else if (event instanceof SessionDisconnectEvent) { + onSessionDisconnect((SessionDisconnectEvent) event); + } + } + + private void onSessionConnected(SessionConnectedEvent event) { + final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId(); + final Player player = new Player(sessionId); + log.info("Player connected: {}", player); + playerBySession.put(sessionId, player); + synchronized (listeners) { + for (Listener listener : listeners) { + listener.playerJoined(player); + } + } + } + + private void onSessionDisconnect(SessionDisconnectEvent event) { + final String sessionId = event.getSessionId(); + final Player player = playerBySession.remove(sessionId); + log.info("Player disconnected: {}", player); + synchronized (listeners) { + for (Listener listener : listeners) { + listener.playerLeft(player); + } + } + } + + public void removeListener(Listener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + +} diff --git a/server/src/main/java/lingo/server/WordRepository.java b/server/src/main/java/lingo/server/WordRepository.java index eaad226..abb2395 100644 --- a/server/src/main/java/lingo/server/WordRepository.java +++ b/server/src/main/java/lingo/server/WordRepository.java @@ -1,39 +1,39 @@ -package lingo.server; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import org.springframework.stereotype.Component; - -import lingo.common.WordReader; - -@Component -public class WordRepository { - - private final Set guesses; - - private final List words; - - public WordRepository() throws IOException { - guesses = WordReader.readFileToSet("/guesses.txt"); - words = WordReader.readFileToList("/words.txt"); - } - - /** - * Returns the set of acceptable guesses (unmodifiable). - */ - public Set getGuesses() { - return Collections.unmodifiableSet(guesses); - } - - /** - * Returns a copy of the list of potential answers (OK to shuffle it). - */ - public List getWords() { - return new ArrayList<>(words); - } - -} +package lingo.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.springframework.stereotype.Component; + +import lingo.common.WordReader; + +@Component +public class WordRepository { + + private final Set guesses; + + private final List words; + + public WordRepository() throws IOException { + guesses = WordReader.readFileToSet("/guesses.txt"); + words = WordReader.readFileToList("/words.txt"); + } + + /** + * Returns the set of acceptable guesses (unmodifiable). + */ + public Set getGuesses() { + return Collections.unmodifiableSet(guesses); + } + + /** + * Returns a copy of the list of potential answers (OK to shuffle it). + */ + public List getWords() { + return new ArrayList<>(words); + } + +} diff --git a/server/src/main/resources/static/cube-grid.css b/server/src/main/resources/static/cube-grid.css index edaddfe..bb5dc39 100644 --- a/server/src/main/resources/static/cube-grid.css +++ b/server/src/main/resources/static/cube-grid.css @@ -1,200 +1,200 @@ -/* Based on http://tobiasahlin.com/spinkit */ - -.sk-cube-grid { - width: 200px; /* Should be 5x the width below */ - height: 240px; /* Should be 6x the height below */ - margin: 0 auto; -} - -.sk-cube-grid .sk-cube { - width: 40px; - height: 40px; - float: left; - -webkit-animation: sk-cubeGridScaleDelay 2s infinite ease-in-out; - animation: sk-cubeGridScaleDelay 2s infinite ease-in-out; -} - -/* -0.0s - 26 -0.1s - 27, 21 -0.2s - 28, 22, 16 -0.3s - 29, 23, 17, 11 -0.4s - 30, 24, 18, 12, 06 -0.5s - 25, 19, 13, 07, 01 -0.6s - 20, 14, 08, 02 -0.7s - 15, 09, 03 -0.8s - 10, 04 -0.9s - 05 -*/ - -/* 0 second delay */ -.sk-cube-grid .sk-cube26 { - -webkit-animation-delay: 0s; - animation-delay: 0s; -} - -/* 0.1 second delay */ -.sk-cube-grid .sk-cube27 { - -webkit-animation-delay: 0.1s; - animation-delay: 0.1s; -} -.sk-cube-grid .sk-cube21 { - -webkit-animation-delay: 0.1s; - animation-delay: 0.1s; -} - -/* 0.2 second delay */ -.sk-cube-grid .sk-cube28 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; -} -.sk-cube-grid .sk-cube22 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; -} -.sk-cube-grid .sk-cube16 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; -} - -/* 0.3 second delay */ -.sk-cube-grid .sk-cube29 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} -.sk-cube-grid .sk-cube23 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} -.sk-cube-grid .sk-cube17 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} -.sk-cube-grid .sk-cube11 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; -} - -/* 0.4 second delay */ -.sk-cube-grid .sk-cube30 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube24 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube18 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube12 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} -.sk-cube-grid .sk-cube6 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; -} - -/* 0.5 second delay */ -.sk-cube-grid .sk-cube25 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube19 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube13 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube7 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} -.sk-cube-grid .sk-cube1 { - -webkit-animation-delay: 0.5s; - animation-delay: 0.5s; -} - -/* 0.6 second delay */ -.sk-cube-grid .sk-cube20 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} -.sk-cube-grid .sk-cube14 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} -.sk-cube-grid .sk-cube8 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} -.sk-cube-grid .sk-cube2 { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; -} - -/* 0.7 second delay */ -.sk-cube-grid .sk-cube15 { - -webkit-animation-delay: 0.7s; - animation-delay: 0.7s; -} -.sk-cube-grid .sk-cube9 { - -webkit-animation-delay: 0.7s; - animation-delay: 0.7s; -} -.sk-cube-grid .sk-cube3 { - -webkit-animation-delay: 0.7s; - animation-delay: 0.7s; -} - -/* 0.8 second delay */ -.sk-cube-grid .sk-cube10 { - -webkit-animation-delay: 0.8s; - animation-delay: 0.8s; -} -.sk-cube-grid .sk-cube4 { - -webkit-animation-delay: 0.8s; - animation-delay: 0.8s; -} - -/* 0.9 second delay */ -.sk-cube-grid .sk-cube5 { - -webkit-animation-delay: 0.9s; - animation-delay: 0.9s; -} - -@-webkit-keyframes sk-cubeGridScaleDelay { - 0%, 100% { - background-color: orange; - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } 70% { - background-color: yellow; - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } 35% { - background-color: greenyellow; - -webkit-transform: scale3D(0, 0, 1); - transform: scale3D(0, 0, 1); - } -} - -@keyframes sk-cubeGridScaleDelay { - 0%, 100% { - background-color: orange; - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } 70% { - background-color: yellow; - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } 35% { - background-color: greenyellow; - -webkit-transform: scale3D(0, 0, 1); - transform: scale3D(0, 0, 1); - } -} +/* Based on http://tobiasahlin.com/spinkit */ + +.sk-cube-grid { + width: 200px; /* Should be 5x the width below */ + height: 240px; /* Should be 6x the height below */ + margin: 0 auto; +} + +.sk-cube-grid .sk-cube { + width: 40px; + height: 40px; + float: left; + -webkit-animation: sk-cubeGridScaleDelay 2s infinite ease-in-out; + animation: sk-cubeGridScaleDelay 2s infinite ease-in-out; +} + +/* +0.0s - 26 +0.1s - 27, 21 +0.2s - 28, 22, 16 +0.3s - 29, 23, 17, 11 +0.4s - 30, 24, 18, 12, 06 +0.5s - 25, 19, 13, 07, 01 +0.6s - 20, 14, 08, 02 +0.7s - 15, 09, 03 +0.8s - 10, 04 +0.9s - 05 +*/ + +/* 0 second delay */ +.sk-cube-grid .sk-cube26 { + -webkit-animation-delay: 0s; + animation-delay: 0s; +} + +/* 0.1 second delay */ +.sk-cube-grid .sk-cube27 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} +.sk-cube-grid .sk-cube21 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} + +/* 0.2 second delay */ +.sk-cube-grid .sk-cube28 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.sk-cube-grid .sk-cube22 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.sk-cube-grid .sk-cube16 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +/* 0.3 second delay */ +.sk-cube-grid .sk-cube29 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube23 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube17 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} +.sk-cube-grid .sk-cube11 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; +} + +/* 0.4 second delay */ +.sk-cube-grid .sk-cube30 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube24 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube18 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube12 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} +.sk-cube-grid .sk-cube6 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +/* 0.5 second delay */ +.sk-cube-grid .sk-cube25 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube19 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube13 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube7 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} +.sk-cube-grid .sk-cube1 { + -webkit-animation-delay: 0.5s; + animation-delay: 0.5s; +} + +/* 0.6 second delay */ +.sk-cube-grid .sk-cube20 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube14 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube8 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} +.sk-cube-grid .sk-cube2 { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +/* 0.7 second delay */ +.sk-cube-grid .sk-cube15 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} +.sk-cube-grid .sk-cube9 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} +.sk-cube-grid .sk-cube3 { + -webkit-animation-delay: 0.7s; + animation-delay: 0.7s; +} + +/* 0.8 second delay */ +.sk-cube-grid .sk-cube10 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} +.sk-cube-grid .sk-cube4 { + -webkit-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +/* 0.9 second delay */ +.sk-cube-grid .sk-cube5 { + -webkit-animation-delay: 0.9s; + animation-delay: 0.9s; +} + +@-webkit-keyframes sk-cubeGridScaleDelay { + 0%, 100% { + background-color: orange; + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 70% { + background-color: yellow; + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + background-color: greenyellow; + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} + +@keyframes sk-cubeGridScaleDelay { + 0%, 100% { + background-color: orange; + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 70% { + background-color: yellow; + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + background-color: greenyellow; + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} diff --git a/server/src/main/resources/static/index.html b/server/src/main/resources/static/index.html index 2dec598..7c59814 100644 --- a/server/src/main/resources/static/index.html +++ b/server/src/main/resources/static/index.html @@ -1,71 +1,71 @@ - - - - -Lingo - - - - - -
-
-
Lingo
-
-
-

What is your name?

- -

{{ usernameError }}

-
-
-
-
-
-
-

Games

-
-
-
There are no games
- -
-
- - -
-
-
-
-
- -
-
-
-
-
-
-
-
Chat
-
-
- {{ message.sender }} - {{ message.body }} -
-
- -
-
- - - - - - + + + + +Lingo + + + + + +
+
+
Lingo
+
+
+

What is your name?

+ +

{{ usernameError }}

+
+
+
+
+
+
+

Games

+
+
+
There are no games
+ +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
+
Chat
+
+
+ {{ message.sender }} + {{ message.body }} +
+
+ +
+
+ + + + + + diff --git a/server/src/main/resources/static/layout.css b/server/src/main/resources/static/layout.css index 60d3efe..322d208 100644 --- a/server/src/main/resources/static/layout.css +++ b/server/src/main/resources/static/layout.css @@ -1,98 +1,98 @@ -/* Generic layout rules */ - -body { - margin: 0; -} - -.row, .column { - overflow: hidden; - position: absolute; -} - -.row { - left: 0; - right: 0; -} - -.column { - top: 0; - bottom: 0; -} - -.scroll-x { - overflow-x: auto; -} - -.scroll-y { - overflow-y: auto; -} - -/* Specific layout rules */ - -.main.column { - left: 0; - right: 300px; -} - -.chat.column { - width: 300px; - right: 0; -} - -.header.row { - height: 75px; - line-height: 75px; -} - -.body.row { - top: 75px; - bottom: 66px; -} - -.body.row.nofooter { - bottom: 0; -} - -.body.row.noheader { - top: 0; -} - -.footer.row { - height: 66px; - bottom: 0; -} - -.chat.column { - border-left: 1px solid black; -} - -.chat.column .body { - padding: 0 10px; -} - -.chat.column .footer { - border-top: 1px solid black; - box-sizing: border-box; - padding: 10px; - padding-bottom: 5px; -} - -.lobby.column { - width: 300px; - left: 0; -} - -.lobby.column .body { - padding: 0 10px; -} - -.lobby.column.primary { - width: inherit; - right: 0; -} - -.game.column.primary { - left: 300px; - right: 0; - border-left: 1px solid black; -} +/* Generic layout rules */ + +body { + margin: 0; +} + +.row, .column { + overflow: hidden; + position: absolute; +} + +.row { + left: 0; + right: 0; +} + +.column { + top: 0; + bottom: 0; +} + +.scroll-x { + overflow-x: auto; +} + +.scroll-y { + overflow-y: auto; +} + +/* Specific layout rules */ + +.main.column { + left: 0; + right: 300px; +} + +.chat.column { + width: 300px; + right: 0; +} + +.header.row { + height: 75px; + line-height: 75px; +} + +.body.row { + top: 75px; + bottom: 66px; +} + +.body.row.nofooter { + bottom: 0; +} + +.body.row.noheader { + top: 0; +} + +.footer.row { + height: 66px; + bottom: 0; +} + +.chat.column { + border-left: 1px solid black; +} + +.chat.column .body { + padding: 0 10px; +} + +.chat.column .footer { + border-top: 1px solid black; + box-sizing: border-box; + padding: 10px; + padding-bottom: 5px; +} + +.lobby.column { + width: 300px; + left: 0; +} + +.lobby.column .body { + padding: 0 10px; +} + +.lobby.column.primary { + width: inherit; + right: 0; +} + +.game.column.primary { + left: 300px; + right: 0; + border-left: 1px solid black; +} diff --git a/server/src/main/resources/static/practice.css b/server/src/main/resources/static/practice.css index 5dbffa3..18aa559 100644 --- a/server/src/main/resources/static/practice.css +++ b/server/src/main/resources/static/practice.css @@ -1,24 +1,24 @@ -canvas { - display: block; - margin: 0 auto; -} - -#skipDiv { - margin-top: 20px; - text-align: center -} - -#skipButton { - width: 200px; - font-size: 28px; - font-variant: small-caps; - background-color: black; - color: white; - cursor: pointer; - padding: 10px; - border: none; -} - -.hidden { - display: none; -} +canvas { + display: block; + margin: 0 auto; +} + +#skipDiv { + margin-top: 20px; + text-align: center +} + +#skipButton { + width: 200px; + font-size: 28px; + font-variant: small-caps; + background-color: black; + color: white; + cursor: pointer; + padding: 10px; + border: none; +} + +.hidden { + display: none; +} diff --git a/server/src/main/resources/static/practice.html b/server/src/main/resources/static/practice.html index 35f2e3f..eaf5dba 100644 --- a/server/src/main/resources/static/practice.html +++ b/server/src/main/resources/static/practice.html @@ -1,18 +1,18 @@ - - - - -Lingo - - - - - - - - - - - + + + + +Lingo + + + + + + + + + + + diff --git a/server/src/main/resources/static/style.css b/server/src/main/resources/static/style.css index bf4efb9..b47072c 100644 --- a/server/src/main/resources/static/style.css +++ b/server/src/main/resources/static/style.css @@ -1,187 +1,187 @@ -/* Common rules */ - -.centered { - display: block; - margin: 0 auto; -} - -.hidden { - display: none; -} - -[v-cloak] { - display: none; -} - -body { - font-family: sans-serif; - background: linen; -} - -button { - padding: 6px 12px; -} - -button:hover:enabled { - cursor: pointer; -} - -button:hover:disabled { - cursor: not-allowed; -} - -/* Main area */ - -.header { - font-size: 1.6em; - color: white; - background: steelblue; - text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); - padding: 0 0.5em; -} - -/* Chat pane */ - -.message-item { - border-top: 1px solid black; - padding: 10px; - position: relative; - overflow-wrap: break-word; - word-wrap: break-word; - white-space: pre-line; -} - -.message-item:first-child { - border-top: none; -} - -.log { - color: green; -} - -.chat.column textarea { - width: 100%; - height: 100%; - font-size: 14px; - background-color: linen; - border-color: transparent; - border-style: none; - outline: none; - padding: 0; - resize: none; -} - -.chat.column textarea::-webkit-input-placeholder { - color: steelblue; -} - -.chat.column textarea::-moz-placeholder { - color: steelblue; -} - -.chat.column textarea:-ms-input-placeholder { - color: steelblue; -} - -/* Lobby pane */ - -.panel { - margin: 20px 0; - border: 1px solid transparent; - border-color: #ddd; - border-radius: 4px; -} - -.panel-heading { - background-color: lightgray; - border-color: #ddd; - padding: 10px 15px; -} - -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: steelblue; -} - -.list-group { - margin-bottom: 0; -} - -.panel>.list-group .list-group-item { - font-size: 1em; - border-width: 1px 0; - border-radius: 0; - outline: none; -} - -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: white; - border: 1px solid #ddd; -} - -button.list-group-item { - width: 100%; - text-align: left; -} - -button.list-group-item:hover { - background-color: lightblue; -} - -button.list-group-item:disabled { - color: black; -} - -button.list-group-item:hover:disabled { - background-color: white; - cursor: default; -} - -.button { - width: 120px; - margin-left: 10px; - border: none; - outline: none; -} - -.create.button { - background-color: steelblue; - color: white; -} - -.leave.button { - background-color: lightcoral; - color: black; -} - -/* Nickname pane */ - -.form { - padding-top: 48px; - text-align: center; - font-weight: 100; -} - -.error-message { - color: red; - font-size: 14px; -} - -.form-control { - width: 300px; - font-size: 200%; - letter-spacing: 3px; - background-color: transparent; - border: none; - border-bottom: 2px solid black; - color: steelblue; - outline: none; - padding-bottom: 15px; - text-align: center; -} +/* Common rules */ + +.centered { + display: block; + margin: 0 auto; +} + +.hidden { + display: none; +} + +[v-cloak] { + display: none; +} + +body { + font-family: sans-serif; + background: linen; +} + +button { + padding: 6px 12px; +} + +button:hover:enabled { + cursor: pointer; +} + +button:hover:disabled { + cursor: not-allowed; +} + +/* Main area */ + +.header { + font-size: 1.6em; + color: white; + background: steelblue; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); + padding: 0 0.5em; +} + +/* Chat pane */ + +.message-item { + border-top: 1px solid black; + padding: 10px; + position: relative; + overflow-wrap: break-word; + word-wrap: break-word; + white-space: pre-line; +} + +.message-item:first-child { + border-top: none; +} + +.log { + color: green; +} + +.chat.column textarea { + width: 100%; + height: 100%; + font-size: 14px; + background-color: linen; + border-color: transparent; + border-style: none; + outline: none; + padding: 0; + resize: none; +} + +.chat.column textarea::-webkit-input-placeholder { + color: steelblue; +} + +.chat.column textarea::-moz-placeholder { + color: steelblue; +} + +.chat.column textarea:-ms-input-placeholder { + color: steelblue; +} + +/* Lobby pane */ + +.panel { + margin: 20px 0; + border: 1px solid transparent; + border-color: #ddd; + border-radius: 4px; +} + +.panel-heading { + background-color: lightgray; + border-color: #ddd; + padding: 10px 15px; +} + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: steelblue; +} + +.list-group { + margin-bottom: 0; +} + +.panel>.list-group .list-group-item { + font-size: 1em; + border-width: 1px 0; + border-radius: 0; + outline: none; +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: white; + border: 1px solid #ddd; +} + +button.list-group-item { + width: 100%; + text-align: left; +} + +button.list-group-item:hover { + background-color: lightblue; +} + +button.list-group-item:disabled { + color: black; +} + +button.list-group-item:hover:disabled { + background-color: white; + cursor: default; +} + +.button { + width: 120px; + margin-left: 10px; + border: none; + outline: none; +} + +.create.button { + background-color: steelblue; + color: white; +} + +.leave.button { + background-color: lightcoral; + color: black; +} + +/* Nickname pane */ + +.form { + padding-top: 48px; + text-align: center; + font-weight: 100; +} + +.error-message { + color: red; + font-size: 14px; +} + +.form-control { + width: 300px; + font-size: 200%; + letter-spacing: 3px; + background-color: transparent; + border: none; + border-bottom: 2px solid black; + color: steelblue; + outline: none; + padding-bottom: 15px; + text-align: center; +}