Convert CRLF to LF

This commit is contained in:
Charles Gould 2019-02-10 15:13:40 -05:00
parent c526f1bf3b
commit a5d9c7eee6
39 changed files with 2882 additions and 2882 deletions

View File

@ -1,22 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.charego</groupId> <groupId>com.charego</groupId>
<artifactId>lingo-websocket</artifactId> <artifactId>lingo-websocket</artifactId>
<version>1.0</version> <version>1.0</version>
</parent> </parent>
<artifactId>lingo-websocket-client-api</artifactId> <artifactId>lingo-websocket-client-api</artifactId>
<name>Lingo WebSocket :: Client API</name> <name>Lingo WebSocket :: Client API</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<version>${project.version}</version> <version>${project.version}</version>
<artifactId>lingo-websocket-common</artifactId> <artifactId>lingo-websocket-common</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,35 +1,35 @@
package lingo.client.api; package lingo.client.api;
public class Destinations { public class Destinations {
public static final String CHAT = topicDestination("chat"); public static final String CHAT = topicDestination("chat");
public static final String GAME_CLOSED = topicDestination("gameClosed"); public static final String GAME_CLOSED = topicDestination("gameClosed");
public static final String GAME_HOSTED = topicDestination("gameHosted"); public static final String GAME_HOSTED = topicDestination("gameHosted");
public static final String GAME_JOINED = topicDestination("gameJoined"); public static final String GAME_JOINED = topicDestination("gameJoined");
public static final String GAME_LEFT = topicDestination("gameLeft"); public static final String GAME_LEFT = topicDestination("gameLeft");
public static final String OPPONENT_JOINED = topicDestination("opponentJoined"); public static final String OPPONENT_JOINED = topicDestination("opponentJoined");
public static final String OPPONENT_REPORTS = topicDestination("opponentReports"); public static final String OPPONENT_REPORTS = topicDestination("opponentReports");
public static final String PLAYER_REPORTS = topicDestination("playerReports"); public static final String PLAYER_REPORTS = topicDestination("playerReports");
public static final String PRACTICE_GAME = topicDestination("practiceGame"); public static final String PRACTICE_GAME = topicDestination("practiceGame");
public static final String PRACTICE_REPORTS = topicDestination("practiceReports"); public static final String PRACTICE_REPORTS = topicDestination("practiceReports");
public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped"); public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped");
public static final String SESSION_USERNAME = topicDestination("sessionUsername"); public static final String SESSION_USERNAME = topicDestination("sessionUsername");
public static final String USER_JOINED = topicDestination("userJoined"); public static final String USER_JOINED = topicDestination("userJoined");
private static String topicDestination(String suffix) { private static String topicDestination(String suffix) {
return "/topic/" + suffix; return "/topic/" + suffix;
} }
} }

View File

@ -1,43 +1,43 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.charego</groupId> <groupId>com.charego</groupId>
<artifactId>lingo-websocket</artifactId> <artifactId>lingo-websocket</artifactId>
<version>1.0</version> <version>1.0</version>
</parent> </parent>
<artifactId>lingo-websocket-client</artifactId> <artifactId>lingo-websocket-client</artifactId>
<name>Lingo WebSocket :: Client</name> <name>Lingo WebSocket :: Client</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<version>${project.version}</version> <version>${project.version}</version>
<artifactId>lingo-websocket-client-api</artifactId> <artifactId>lingo-websocket-client-api</artifactId>
</dependency> </dependency>
<!-- Web --> <!-- Web -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>com.zenjava</groupId> <groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId> <artifactId>javafx-maven-plugin</artifactId>
<version>8.8.3</version> <version>8.8.3</version>
<configuration> <configuration>
<mainClass>lingo.client.bootstrap.LingoClient</mainClass> <mainClass>lingo.client.bootstrap.LingoClient</mainClass>
<additionalAppResources>src/main/config</additionalAppResources> <additionalAppResources>src/main/config</additionalAppResources>
<copyAdditionalAppResourcesToJar>true</copyAdditionalAppResourcesToJar> <copyAdditionalAppResourcesToJar>true</copyAdditionalAppResourcesToJar>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -1,9 +1,9 @@
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# Development # Development
web.base.url: http://localhost:8080 web.base.url: http://localhost:8080
web.socket.url: ws://localhost:8080/sockjs web.socket.url: ws://localhost:8080/sockjs
# Production # Production
#web.base.url: http://lingo.charego.com #web.base.url: http://lingo.charego.com
#web.socket.url: ws://lingo.charego.com/sockjs #web.socket.url: ws://lingo.charego.com/sockjs

View File

@ -1,65 +1,65 @@
package lingo.client.bootstrap; package lingo.client.bootstrap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
@SpringBootApplication @SpringBootApplication
public class LingoClient extends Application { public class LingoClient extends Application {
private Parent root; private Parent root;
public static void main(final String[] args) { public static void main(final String[] args) {
Application.launch(args); Application.launch(args);
} }
@Override @Override
public void init() throws Exception { public void init() throws Exception {
ConfigurableApplicationContext context = startSpringApplication(); ConfigurableApplicationContext context = startSpringApplication();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Lingo.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Lingo.fxml"));
loader.setControllerFactory(clazz -> context.getBean(clazz)); loader.setControllerFactory(clazz -> context.getBean(clazz));
root = loader.load(); root = loader.load();
} }
@Override @Override
public void start(Stage stage) throws Exception { public void start(Stage stage) throws Exception {
// Close the Spring context when the client is closed. // Close the Spring context when the client is closed.
stage.setOnCloseRequest(e -> { stage.setOnCloseRequest(e -> {
stage.close(); stage.close();
System.exit(0); System.exit(0);
}); });
Scene scene = new Scene(root); Scene scene = new Scene(root);
scene.getStylesheets().add("/style.css"); scene.getStylesheets().add("/style.css");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/lingo.png"))); stage.getIcons().add(new Image(getClass().getResourceAsStream("/lingo.png")));
stage.setResizable(false); stage.setResizable(false);
stage.setScene(scene); stage.setScene(scene);
stage.setTitle("Lingo"); stage.setTitle("Lingo");
stage.show(); stage.show();
} }
private ConfigurableApplicationContext startSpringApplication() { private ConfigurableApplicationContext startSpringApplication() {
SpringApplication application = new SpringApplication(LingoClient.class); SpringApplication application = new SpringApplication(LingoClient.class);
String[] args = getParameters().getRaw().stream().toArray(String[]::new); String[] args = getParameters().getRaw().stream().toArray(String[]::new);
application.setHeadless(false); application.setHeadless(false);
application.setWebEnvironment(false); application.setWebEnvironment(false);
return application.run(args); return application.run(args);
} }
@Bean @Bean
public ExecutorService executorService() { public ExecutorService executorService() {
return Executors.newFixedThreadPool(5, new CustomizableThreadFactory("ClientThread-")); return Executors.newFixedThreadPool(5, new CustomizableThreadFactory("ClientThread-"));
} }
} }

View File

@ -1,102 +1,102 @@
package lingo.client.bootstrap; package lingo.client.bootstrap;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.stage.Stage; import javafx.stage.Stage;
import lingo.client.multiplayer.MultiplayerConfig; import lingo.client.multiplayer.MultiplayerConfig;
import lingo.client.multiplayer.MultiplayerPresenter; import lingo.client.multiplayer.MultiplayerPresenter;
import lingo.client.singleplayer.SinglePlayerPresenter; import lingo.client.singleplayer.SinglePlayerPresenter;
import lingo.client.util.FxmlController; import lingo.client.util.FxmlController;
import lingo.common.WordReader; import lingo.common.WordReader;
@Component @Component
public class LingoPresenter implements FxmlController { public class LingoPresenter implements FxmlController {
private static final Logger log = LoggerFactory.getLogger(LingoPresenter.class); private static final Logger log = LoggerFactory.getLogger(LingoPresenter.class);
@Autowired @Autowired
private ApplicationContext bootstrapContext; private ApplicationContext bootstrapContext;
@Autowired @Autowired
private ExecutorService executorService; private ExecutorService executorService;
@FXML @FXML
private BorderPane content; private BorderPane content;
@FXML @FXML
private BorderPane gameModeChooser; private BorderPane gameModeChooser;
@FXML @FXML
private void exit(ActionEvent event) { private void exit(ActionEvent event) {
Stage stage = (Stage) content.getScene().getWindow(); Stage stage = (Stage) content.getScene().getWindow();
stage.close(); stage.close();
System.exit(0); System.exit(0);
} }
@Override @Override
public void initialize() { public void initialize() {
// No initialization needed // No initialization needed
} }
@FXML @FXML
private void showMultiplayer(ActionEvent event) { private void showMultiplayer(ActionEvent event) {
log.info("Launching multiplayer..."); log.info("Launching multiplayer...");
executorService.execute(() -> { executorService.execute(() -> {
AnnotationConfigApplicationContext multiplayerContext = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext multiplayerContext = new AnnotationConfigApplicationContext();
multiplayerContext.setParent(bootstrapContext); multiplayerContext.setParent(bootstrapContext);
multiplayerContext.scan(MultiplayerConfig.class.getPackage().getName()); multiplayerContext.scan(MultiplayerConfig.class.getPackage().getName());
multiplayerContext.refresh(); multiplayerContext.refresh();
MultiplayerPresenter presenter = multiplayerContext.getBean(MultiplayerPresenter.class); MultiplayerPresenter presenter = multiplayerContext.getBean(MultiplayerPresenter.class);
presenter.setOnBackButtonPressed(e -> { presenter.setOnBackButtonPressed(e -> {
multiplayerContext.close(); multiplayerContext.close();
content.setCenter(gameModeChooser); content.setCenter(gameModeChooser);
}); });
Platform.runLater(() -> { Platform.runLater(() -> {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LingoMultiplayer.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LingoMultiplayer.fxml"));
loader.setControllerFactory(clazz -> multiplayerContext.getBean(clazz)); loader.setControllerFactory(clazz -> multiplayerContext.getBean(clazz));
try { try {
content.setCenter(loader.load()); content.setCenter(loader.load());
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to load multiplayer", e); log.error("Failed to load multiplayer", e);
} }
}); });
}); });
} }
@FXML @FXML
private void showSinglePlayer(ActionEvent event) { private void showSinglePlayer(ActionEvent event) {
log.info("Launching single player..."); log.info("Launching single player...");
// TODO: Is there a memory leak here? // TODO: Is there a memory leak here?
try { try {
Set<String> guesses = WordReader.readFileToSet("/guesses.txt"); Set<String> guesses = WordReader.readFileToSet("/guesses.txt");
List<String> words = WordReader.readFileToList("/words.txt"); List<String> words = WordReader.readFileToList("/words.txt");
SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> { SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> {
content.setCenter(gameModeChooser); content.setCenter(gameModeChooser);
}); });
content.setCenter(presenter.getNode()); content.setCenter(presenter.getNode());
presenter.startGame(); presenter.startGame();
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to load single player", e); log.error("Failed to load single player", e);
} }
} }
} }

View File

@ -1,60 +1,60 @@
package lingo.client.multiplayer; package lingo.client.multiplayer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.boot.web.client.RootUriTemplateHandler; import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.socket.client.WebSocketClient; import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient; import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient; import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport; import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport; import org.springframework.web.socket.sockjs.client.WebSocketTransport;
@Configuration @Configuration
public class MultiplayerConfig { public class MultiplayerConfig {
@Bean @Bean
public WebSocketClient webSocketClient() { public WebSocketClient webSocketClient() {
WebSocketClient webSocketClient = new StandardWebSocketClient(); WebSocketClient webSocketClient = new StandardWebSocketClient();
List<Transport> transports = new ArrayList<>(); List<Transport> transports = new ArrayList<>();
transports.add(new WebSocketTransport(webSocketClient)); transports.add(new WebSocketTransport(webSocketClient));
SockJsClient sockJsClient = new SockJsClient(transports); SockJsClient sockJsClient = new SockJsClient(transports);
return sockJsClient; return sockJsClient;
} }
@Bean @Bean
public WebSocketStompClient stompClient(WebSocketClient webSocketClient, MessageConverter messageConverter) { public WebSocketStompClient stompClient(WebSocketClient webSocketClient, MessageConverter messageConverter) {
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(messageConverter); stompClient.setMessageConverter(messageConverter);
stompClient.setTaskScheduler(new ThreadPoolTaskScheduler()); stompClient.setTaskScheduler(new ThreadPoolTaskScheduler());
return stompClient; return stompClient;
} }
@Bean @Bean
public MessageConverter messageConverter() { public MessageConverter messageConverter() {
List<MessageConverter> converters = new ArrayList<>(); List<MessageConverter> converters = new ArrayList<>();
converters.add(new StringMessageConverter()); converters.add(new StringMessageConverter());
converters.add(new ByteArrayMessageConverter()); converters.add(new ByteArrayMessageConverter());
converters.add(new MappingJackson2MessageConverter()); converters.add(new MappingJackson2MessageConverter());
return new CompositeMessageConverter(converters); return new CompositeMessageConverter(converters);
} }
@Bean @Bean
public RestTemplate restTemplate(Environment env) { public RestTemplate restTemplate(Environment env) {
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url"))); restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url")));
return restTemplate; return restTemplate;
} }
} }

View File

@ -1,426 +1,426 @@
package lingo.client.multiplayer; package lingo.client.multiplayer;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.messaging.simp.stomp.StompFrameHandler; import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders; import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.VPos; import javafx.geometry.VPos;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment; import javafx.scene.text.TextAlignment;
import javafx.scene.web.WebView; import javafx.scene.web.WebView;
import lingo.client.api.Destinations; import lingo.client.api.Destinations;
import lingo.client.util.FxmlController; import lingo.client.util.FxmlController;
import lingo.client.view.Board; import lingo.client.view.Board;
import lingo.client.view.OpponentBoard; import lingo.client.view.OpponentBoard;
import lingo.client.view.PlayerBoard; import lingo.client.view.PlayerBoard;
import lingo.common.Game; import lingo.common.Game;
import lingo.common.GameLeftMessage; import lingo.common.GameLeftMessage;
import lingo.common.Report; import lingo.common.Report;
@Component @Component
public class MultiplayerPresenter implements FxmlController { public class MultiplayerPresenter implements FxmlController {
private static final Logger log = LoggerFactory.getLogger(MultiplayerPresenter.class); private static final Logger log = LoggerFactory.getLogger(MultiplayerPresenter.class);
private static final double MARGIN_BOTTOM = 75; private static final double MARGIN_BOTTOM = 75;
@FXML @FXML
private Button backButton; private Button backButton;
@FXML @FXML
private Canvas canvas; private Canvas canvas;
@FXML @FXML
private StackPane contentPane; private StackPane contentPane;
@FXML @FXML
private WebView webView; private WebView webView;
@Autowired @Autowired
private ExecutorService executorService; private ExecutorService executorService;
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Autowired @Autowired
private StompTemplate stompTemplate; private StompTemplate stompTemplate;
private EventHandler<ActionEvent> backButtonHandler; private EventHandler<ActionEvent> backButtonHandler;
private GraphicsContext gc; private GraphicsContext gc;
private String lastWord; private String lastWord;
private PlayerBoard playerBoard; private PlayerBoard playerBoard;
private OpponentBoard opponentBoard; private OpponentBoard opponentBoard;
private final CountDownLatch subscriptionsLatch = new CountDownLatch(7); private final CountDownLatch subscriptionsLatch = new CountDownLatch(7);
private String username; private String username;
private String opponentUsername; private String opponentUsername;
private void clearBoards(boolean clearScore) { private void clearBoards(boolean clearScore) {
playerBoard.clearBoard(); playerBoard.clearBoard();
opponentBoard.clearBoard(); opponentBoard.clearBoard();
if (clearScore) { if (clearScore) {
playerBoard.clearScore(); playerBoard.clearScore();
opponentBoard.clearScore(); opponentBoard.clearScore();
} }
} }
private void drawLastWord() { private void drawLastWord() {
if (lastWord != null) { if (lastWord != null) {
double x = canvas.getWidth() / 2; double x = canvas.getWidth() / 2;
double y = canvas.getHeight() - MARGIN_BOTTOM / 2; double y = canvas.getHeight() - MARGIN_BOTTOM / 2;
gc.setFill(Color.BLACK); gc.setFill(Color.BLACK);
gc.fillText("Previous word: " + lastWord.toUpperCase(), x, y); gc.fillText("Previous word: " + lastWord.toUpperCase(), x, y);
} }
} }
@Override @Override
public void initialize() { public void initialize() {
// Needed for key event handling // Needed for key event handling
canvas.setFocusTraversable(true); canvas.setFocusTraversable(true);
gc = canvas.getGraphicsContext2D(); gc = canvas.getGraphicsContext2D();
gc.setFont(Font.font(24)); gc.setFont(Font.font(24));
gc.setTextAlign(TextAlignment.CENTER); gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER); gc.setTextBaseline(VPos.CENTER);
playerBoard = new PlayerBoard(canvas, 50, 50); playerBoard = new PlayerBoard(canvas, 50, 50);
opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50); opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50);
Platform.runLater(() -> { Platform.runLater(() -> {
String html = getClass().getResource("/cube-grid.html").toExternalForm(); String html = getClass().getResource("/cube-grid.html").toExternalForm();
String css = getClass().getResource("/cube-grid.css").toExternalForm(); String css = getClass().getResource("/cube-grid.css").toExternalForm();
webView.getEngine().load(html); webView.getEngine().load(html);
webView.getEngine().setUserStyleSheetLocation(css); webView.getEngine().setUserStyleSheetLocation(css);
webView.setContextMenuEnabled(false); webView.setContextMenuEnabled(false);
repaint(); repaint();
}); });
backButton.setOnAction(backButtonHandler); backButton.setOnAction(backButtonHandler);
executorService.execute(() -> { executorService.execute(() -> {
while (subscriptionsLatch.getCount() != 0) { while (subscriptionsLatch.getCount() != 0) {
try { try {
subscriptionsLatch.await(); subscriptionsLatch.await();
} catch (InterruptedException ok) { } catch (InterruptedException ok) {
ok.printStackTrace(); ok.printStackTrace();
} }
} }
username = UUID.randomUUID().toString().substring(0, 8); username = UUID.randomUUID().toString().substring(0, 8);
stompTemplate.getSession().send("/app/setUsername", username); stompTemplate.getSession().send("/app/setUsername", username);
Collection<Game> games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody(); Collection<Game> games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody();
boolean joinedGame = false; boolean joinedGame = false;
for (Game game : games) { for (Game game : games) {
if (game.getPlayerTwo() == null) { if (game.getPlayerTwo() == null) {
stompTemplate.getSession().send("/app/joinGame", game.getId()); stompTemplate.getSession().send("/app/joinGame", game.getId());
joinedGame = true; joinedGame = true;
break; break;
} }
} }
if (!joinedGame) { if (!joinedGame) {
stompTemplate.getSession().send("/app/hostGame", null); stompTemplate.getSession().send("/app/hostGame", null);
} }
}); });
} }
@FXML @FXML
private void keyPressed(KeyEvent e) { private void keyPressed(KeyEvent e) {
final KeyCode keyCode = e.getCode(); final KeyCode keyCode = e.getCode();
if (keyCode == KeyCode.BACK_SPACE) { if (keyCode == KeyCode.BACK_SPACE) {
if (playerBoard.handleBackspace()) { if (playerBoard.handleBackspace()) {
repaint(); repaint();
} }
} else if (keyCode == KeyCode.ENTER) { } else if (keyCode == KeyCode.ENTER) {
final String guess = playerBoard.handleEnter(); final String guess = playerBoard.handleEnter();
if (guess != null) { if (guess != null) {
executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess)); executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess));
repaint(); repaint();
} }
} else if (keyCode.isLetterKey()) { } else if (keyCode.isLetterKey()) {
if (playerBoard.handleLetter(keyCode.getName())) { if (playerBoard.handleLetter(keyCode.getName())) {
repaint(); repaint();
} }
} }
} }
private void newWord(String firstLetter) { private void newWord(String firstLetter) {
playerBoard.setProgress(0, firstLetter.charAt(0)); playerBoard.setProgress(0, firstLetter.charAt(0));
} }
@PostConstruct @PostConstruct
private void postConstruct() { private void postConstruct() {
executorService.execute(() -> { executorService.execute(() -> {
stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(), stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(), stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(), stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(), stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(), stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(), stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(), stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(),
subscription -> subscriptionsLatch.countDown()); subscription -> subscriptionsLatch.countDown());
}); });
} }
private void repaint() { private void repaint() {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
playerBoard.drawBoard(); playerBoard.drawBoard();
opponentBoard.drawBoard(); opponentBoard.drawBoard();
drawLastWord(); drawLastWord();
} }
public void setOnBackButtonPressed(EventHandler<ActionEvent> handler) { public void setOnBackButtonPressed(EventHandler<ActionEvent> handler) {
backButtonHandler = handler; backButtonHandler = handler;
} }
private void showWaitingAnimation(boolean show) { private void showWaitingAnimation(boolean show) {
if (show) { if (show) {
contentPane.getChildren().add(webView); contentPane.getChildren().add(webView);
backButton.toFront(); backButton.toFront();
} else { } else {
contentPane.getChildren().remove(webView); contentPane.getChildren().remove(webView);
} }
} }
private class GameClosedHandler implements StompFrameHandler { private class GameClosedHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return Game.class; return Game.class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Game) payload); handleMessage((Game) payload);
} }
private void handleMessage(Game game) { private void handleMessage(Game game) {
log.debug("{} closed Game {}", game.getPlayerOne(), game.getId()); log.debug("{} closed Game {}", game.getPlayerOne(), game.getId());
} }
} }
private class GameHostedHandler implements StompFrameHandler { private class GameHostedHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return Game.class; return Game.class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Game) payload); handleMessage((Game) payload);
} }
private void handleMessage(Game game) { private void handleMessage(Game game) {
log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId()); log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId());
} }
} }
private class GameJoinedHandler implements StompFrameHandler { private class GameJoinedHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return Game.class; return Game.class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Game) payload); handleMessage((Game) payload);
} }
private void handleMessage(Game game) { private void handleMessage(Game game) {
log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne()); log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne());
} }
} }
private class GameLeftHandler implements StompFrameHandler { private class GameLeftHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return GameLeftMessage.class; return GameLeftMessage.class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((GameLeftMessage) payload); handleMessage((GameLeftMessage) payload);
} }
private void handleMessage(GameLeftMessage message) { private void handleMessage(GameLeftMessage message) {
final Game game = message.getGame(); final Game game = message.getGame();
final String gameLeaver = message.getGameLeaver().getUsername(); final String gameLeaver = message.getGameLeaver().getUsername();
log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne()); log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne());
if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) { if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) {
Platform.runLater(() -> { Platform.runLater(() -> {
clearBoards(true); clearBoards(true);
showWaitingAnimation(true); showWaitingAnimation(true);
opponentUsername = null; opponentUsername = null;
lastWord = null; lastWord = null;
repaint(); repaint();
}); });
} }
} }
} }
private class OpponentJoinedHandler implements StompFrameHandler { private class OpponentJoinedHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return String[].class; return String[].class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((String[]) payload); handleMessage((String[]) payload);
} }
private void handleMessage(String[] message) { private void handleMessage(String[] message) {
final String firstLetter = message[0]; final String firstLetter = message[0];
opponentUsername = message[1]; opponentUsername = message[1];
Platform.runLater(() -> { Platform.runLater(() -> {
clearBoards(true); clearBoards(true);
newWord(firstLetter); newWord(firstLetter);
showWaitingAnimation(false); showWaitingAnimation(false);
repaint(); repaint();
}); });
} }
} }
private class OpponentReportHandler implements StompFrameHandler { private class OpponentReportHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return Report.class; return Report.class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Report) payload); handleMessage((Report) payload);
} }
private void handleMessage(Report report) { private void handleMessage(Report report) {
if (report.isCorrect()) { if (report.isCorrect()) {
onCorrectGuess(report); onCorrectGuess(report);
} else { } else {
onIncorrectGuess(report); onIncorrectGuess(report);
} }
} }
private void onCorrectGuess(Report report) { private void onCorrectGuess(Report report) {
final String guess = report.getGuess(); final String guess = report.getGuess();
final String firstLetter = report.getFirstLetter(); final String firstLetter = report.getFirstLetter();
log.info("Opponent guessed correctly: " + guess); log.info("Opponent guessed correctly: " + guess);
Platform.runLater(() -> { Platform.runLater(() -> {
opponentBoard.addToScore(100); opponentBoard.addToScore(100);
lastWord = guess; lastWord = guess;
clearBoards(false); clearBoards(false);
newWord(firstLetter); newWord(firstLetter);
repaint(); repaint();
}); });
} }
private void onIncorrectGuess(Report report) { private void onIncorrectGuess(Report report) {
final int[] result = report.getResult(); final int[] result = report.getResult();
log.info("Opponent result: " + Arrays.toString(result)); log.info("Opponent result: " + Arrays.toString(result));
Platform.runLater(() -> { Platform.runLater(() -> {
opponentBoard.addResult(result); opponentBoard.addResult(result);
repaint(); repaint();
}); });
} }
} }
private class PlayerReportHandler implements StompFrameHandler { private class PlayerReportHandler implements StompFrameHandler {
@Override @Override
public Type getPayloadType(StompHeaders headers) { public Type getPayloadType(StompHeaders headers) {
return Report.class; return Report.class;
} }
@Override @Override
public void handleFrame(StompHeaders headers, Object payload) { public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Report) payload); handleMessage((Report) payload);
} }
private void handleMessage(Report report) { private void handleMessage(Report report) {
if (report.isCorrect()) { if (report.isCorrect()) {
onCorrectGuess(report); onCorrectGuess(report);
} else { } else {
onIncorrectGuess(report); onIncorrectGuess(report);
} }
} }
private void onCorrectGuess(Report report) { private void onCorrectGuess(Report report) {
final String guess = report.getGuess(); final String guess = report.getGuess();
final String firstLetter = report.getFirstLetter(); final String firstLetter = report.getFirstLetter();
log.info("I guessed correctly!"); log.info("I guessed correctly!");
Platform.runLater(() -> { Platform.runLater(() -> {
playerBoard.addToScore(100); playerBoard.addToScore(100);
lastWord = guess; lastWord = guess;
clearBoards(false); clearBoards(false);
newWord(firstLetter); newWord(firstLetter);
repaint(); repaint();
}); });
} }
private void onIncorrectGuess(Report report) { private void onIncorrectGuess(Report report) {
final String guess = report.getGuess(); final String guess = report.getGuess();
final int[] result = report.getResult(); final int[] result = report.getResult();
log.info("My result: " + Arrays.toString(result)); log.info("My result: " + Arrays.toString(result));
Platform.runLater(() -> { Platform.runLater(() -> {
if (Game.isInvalid(result)) { if (Game.isInvalid(result)) {
playerBoard.addGuess("-----"); playerBoard.addGuess("-----");
} else { } else {
for (int i = 0; i < Game.WORD_LENGTH; i++) { for (int i = 0; i < Game.WORD_LENGTH; i++) {
if (result[i] == Game.CORRECT_CHARACTER) { if (result[i] == Game.CORRECT_CHARACTER) {
playerBoard.setProgress(i, guess.charAt(i)); playerBoard.setProgress(i, guess.charAt(i));
} }
} }
playerBoard.addGuess(guess); playerBoard.addGuess(guess);
} }
playerBoard.addResult(result); playerBoard.addResult(result);
repaint(); repaint();
}); });
} }
} }
private class GameList extends ParameterizedTypeReference<Collection<Game>> { private class GameList extends ParameterizedTypeReference<Collection<Game>> {
// intentionally left empty // intentionally left empty
} }
} }

View File

@ -1,126 +1,126 @@
package lingo.client.multiplayer; package lingo.client.multiplayer;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.simp.stomp.StompFrameHandler; import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders; import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession; import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSession.Subscription; import org.springframework.messaging.simp.stomp.StompSession.Subscription;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.WebSocketStompClient; import org.springframework.web.socket.messaging.WebSocketStompClient;
@Component @Component
public class StompTemplate { public class StompTemplate {
private static final Logger log = LoggerFactory.getLogger(StompTemplate.class); private static final Logger log = LoggerFactory.getLogger(StompTemplate.class);
@Value("${web.socket.url}") @Value("${web.socket.url}")
private String webSocketUrl; private String webSocketUrl;
@Autowired @Autowired
private ExecutorService executorService; private ExecutorService executorService;
@Autowired @Autowired
private WebSocketStompClient stompClient; private WebSocketStompClient stompClient;
private StompSession stompSession; private StompSession stompSession;
private final BlockingQueue<SubscriptionRequest> subscriptionRequests = new LinkedBlockingQueue<>(); private final BlockingQueue<SubscriptionRequest> subscriptionRequests = new LinkedBlockingQueue<>();
public StompSession getSession() { public StompSession getSession() {
/* /*
* TODO: If STOMP session is null or disconnected, create a new * TODO: If STOMP session is null or disconnected, create a new
* connection before returning this field. * connection before returning this field.
*/ */
return stompSession; return stompSession;
} }
@PostConstruct @PostConstruct
private void postConstruct() { private void postConstruct() {
executorService.execute(() -> stompClient.connect(webSocketUrl, new WebSocketSessionHandler())); executorService.execute(() -> stompClient.connect(webSocketUrl, new WebSocketSessionHandler()));
new Thread(new WebSocketSessionListener()).start(); new Thread(new WebSocketSessionListener()).start();
} }
@PreDestroy @PreDestroy
private void preDestroy() { private void preDestroy() {
if (stompSession != null) { if (stompSession != null) {
log.info("Disconnecting from STOMP endpoint..."); log.info("Disconnecting from STOMP endpoint...");
stompSession.disconnect(); stompSession.disconnect();
} }
stompClient.stop(); stompClient.stop();
} }
public void subscribe(String destination, StompFrameHandler handler) { public void subscribe(String destination, StompFrameHandler handler) {
subscribe(destination, handler, null); subscribe(destination, handler, null);
} }
public void subscribe(String destination, StompFrameHandler handler, Consumer<Subscription> callback) { public void subscribe(String destination, StompFrameHandler handler, Consumer<Subscription> callback) {
try { try {
subscriptionRequests.put(new SubscriptionRequest(destination, handler, callback)); subscriptionRequests.put(new SubscriptionRequest(destination, handler, callback));
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.error("Failed to subscribe to destination: {}", destination, e); log.error("Failed to subscribe to destination: {}", destination, e);
} }
} }
private class SubscriptionRequest { private class SubscriptionRequest {
public final String destination; public final String destination;
public final StompFrameHandler handler; public final StompFrameHandler handler;
public final Consumer<Subscription> callback; public final Consumer<Subscription> callback;
public SubscriptionRequest(String destination, StompFrameHandler handler, Consumer<Subscription> callback) { public SubscriptionRequest(String destination, StompFrameHandler handler, Consumer<Subscription> callback) {
this.destination = destination; this.destination = destination;
this.handler = handler; this.handler = handler;
this.callback = callback; this.callback = callback;
} }
public void onSubscribed(Subscription subscription) { public void onSubscribed(Subscription subscription) {
if (callback != null) { if (callback != null) {
callback.accept(subscription); callback.accept(subscription);
} }
} }
} }
private class WebSocketSessionHandler extends StompSessionHandlerAdapter { private class WebSocketSessionHandler extends StompSessionHandlerAdapter {
@Override @Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) { public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
log.info("Connected to STOMP endpoint"); log.info("Connected to STOMP endpoint");
stompSession = session; stompSession = session;
} }
} }
private class WebSocketSessionListener implements Runnable { private class WebSocketSessionListener implements Runnable {
@Override @Override
public void run() { public void run() {
while (true) { while (true) {
if (stompSession == null) { if (stompSession == null) {
try { try {
Thread.sleep(1000L); Thread.sleep(1000L);
} catch (InterruptedException ok) { } catch (InterruptedException ok) {
ok.printStackTrace(); ok.printStackTrace();
} }
continue; continue;
} }
try { try {
final SubscriptionRequest request = subscriptionRequests.take(); final SubscriptionRequest request = subscriptionRequests.take();
final Subscription subscription = stompSession.subscribe(request.destination, request.handler); final Subscription subscription = stompSession.subscribe(request.destination, request.handler);
request.onSubscribed(subscription); request.onSubscribed(subscription);
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.error("Failed to subscribe", e); log.error("Failed to subscribe", e);
} }
} }
} }
} }
} }

View File

@ -1,161 +1,161 @@
package lingo.client.singleplayer; package lingo.client.singleplayer;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.geometry.VPos; import javafx.geometry.VPos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment; import javafx.scene.text.TextAlignment;
import lingo.client.view.PlayerBoard; import lingo.client.view.PlayerBoard;
import lingo.common.Game; import lingo.common.Game;
import lingo.common.Player; import lingo.common.Player;
import lingo.common.Report; import lingo.common.Report;
public class SinglePlayerPresenter { public class SinglePlayerPresenter {
private static final Logger log = LoggerFactory.getLogger(SinglePlayerPresenter.class); private static final Logger log = LoggerFactory.getLogger(SinglePlayerPresenter.class);
private final Button backButton; private final Button backButton;
private final Canvas canvas; private final Canvas canvas;
private final StackPane contentPane; private final StackPane contentPane;
private final GraphicsContext gc; private final GraphicsContext gc;
private final PlayerBoard gameBoard; private final PlayerBoard gameBoard;
private final Game game; private final Game game;
public SinglePlayerPresenter(List<String> words, Set<String> guesses, EventHandler<ActionEvent> backButtonHandler) { public SinglePlayerPresenter(List<String> words, Set<String> guesses, EventHandler<ActionEvent> backButtonHandler) {
backButton = new Button("Back"); backButton = new Button("Back");
backButton.getStyleClass().add("game-nav"); backButton.getStyleClass().add("game-nav");
StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT); StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT);
StackPane.setMargin(backButton, new Insets(0, 0, 10, 10)); StackPane.setMargin(backButton, new Insets(0, 0, 10, 10));
backButton.setOnAction(backButtonHandler); backButton.setOnAction(backButtonHandler);
backButton.setPrefWidth(50); backButton.setPrefWidth(50);
canvas = new Canvas(650, 420); canvas = new Canvas(650, 420);
canvas.setFocusTraversable(true); canvas.setFocusTraversable(true);
canvas.setOnKeyPressed(e -> keyPressed(e)); canvas.setOnKeyPressed(e -> keyPressed(e));
gc = canvas.getGraphicsContext2D(); gc = canvas.getGraphicsContext2D();
gc.setFont(Font.font(24)); gc.setFont(Font.font(24));
gc.setTextAlign(TextAlignment.CENTER); gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER); gc.setTextBaseline(VPos.CENTER);
contentPane = new StackPane(); contentPane = new StackPane();
contentPane.getChildren().add(canvas); contentPane.getChildren().add(canvas);
contentPane.getChildren().add(backButton); contentPane.getChildren().add(backButton);
gameBoard = new PlayerBoard(canvas, 200, 60); gameBoard = new PlayerBoard(canvas, 200, 60);
game = new Game(new Player("solo")); game = new Game(new Player("solo"));
game.setAcceptableGuesses(guesses); game.setAcceptableGuesses(guesses);
game.setPossibleWords(words); game.setPossibleWords(words);
} }
private void clearBoards(boolean clearScore) { private void clearBoards(boolean clearScore) {
gameBoard.clearBoard(); gameBoard.clearBoard();
if (clearScore) { if (clearScore) {
gameBoard.clearScore(); gameBoard.clearScore();
} }
} }
public Node getNode() { public Node getNode() {
return contentPane; return contentPane;
} }
private void keyPressed(KeyEvent e) { private void keyPressed(KeyEvent e) {
final KeyCode keyCode = e.getCode(); final KeyCode keyCode = e.getCode();
if (keyCode == KeyCode.BACK_SPACE) { if (keyCode == KeyCode.BACK_SPACE) {
if (gameBoard.handleBackspace()) { if (gameBoard.handleBackspace()) {
repaint(); repaint();
} }
} else if (keyCode == KeyCode.ENTER) { } else if (keyCode == KeyCode.ENTER) {
final String guess = gameBoard.handleEnter(); final String guess = gameBoard.handleEnter();
if (guess != null) { if (guess != null) {
final int[] result = game.evaluate(guess); final int[] result = game.evaluate(guess);
Report report = new Report(); Report report = new Report();
report.setGuess(guess); report.setGuess(guess);
report.setResult(result); report.setResult(result);
if (Game.isCorrect(result)) { if (Game.isCorrect(result)) {
final String newWord = game.newWord(); final String newWord = game.newWord();
final String firstLetter = String.valueOf(newWord.charAt(0)); final String firstLetter = String.valueOf(newWord.charAt(0));
report.setCorrect(true); report.setCorrect(true);
report.setFirstLetter(firstLetter); report.setFirstLetter(firstLetter);
report.setResult(result); report.setResult(result);
onCorrectGuess(report); onCorrectGuess(report);
} else { } else {
onIncorrectGuess(report); onIncorrectGuess(report);
} }
} }
} else if (keyCode.isLetterKey()) { } else if (keyCode.isLetterKey()) {
if (gameBoard.handleLetter(keyCode.getName())) { if (gameBoard.handleLetter(keyCode.getName())) {
repaint(); repaint();
} }
} }
} }
private void newWord(String firstLetter) { private void newWord(String firstLetter) {
gameBoard.setProgress(0, firstLetter.charAt(0)); gameBoard.setProgress(0, firstLetter.charAt(0));
} }
private void onCorrectGuess(Report report) { private void onCorrectGuess(Report report) {
final String firstLetter = report.getFirstLetter(); final String firstLetter = report.getFirstLetter();
log.info("I guessed correctly!"); log.info("I guessed correctly!");
Platform.runLater(() -> { Platform.runLater(() -> {
gameBoard.addToScore(100); gameBoard.addToScore(100);
clearBoards(false); clearBoards(false);
newWord(firstLetter); newWord(firstLetter);
repaint(); repaint();
}); });
} }
private void onIncorrectGuess(Report report) { private void onIncorrectGuess(Report report) {
final String guess = report.getGuess(); final String guess = report.getGuess();
final int[] result = report.getResult(); final int[] result = report.getResult();
log.info("My result: " + Arrays.toString(result)); log.info("My result: " + Arrays.toString(result));
Platform.runLater(() -> { Platform.runLater(() -> {
if (Game.isInvalid(result)) { if (Game.isInvalid(result)) {
gameBoard.addGuess("-----"); gameBoard.addGuess("-----");
} else { } else {
for (int i = 0; i < Game.WORD_LENGTH; i++) { for (int i = 0; i < Game.WORD_LENGTH; i++) {
if (result[i] == Game.CORRECT_CHARACTER) { if (result[i] == Game.CORRECT_CHARACTER) {
gameBoard.setProgress(i, guess.charAt(i)); gameBoard.setProgress(i, guess.charAt(i));
} }
} }
gameBoard.addGuess(guess); gameBoard.addGuess(guess);
} }
gameBoard.addResult(result); gameBoard.addResult(result);
repaint(); repaint();
}); });
} }
private void repaint() { private void repaint() {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
gameBoard.drawBoard(); gameBoard.drawBoard();
} }
public void startGame() { public void startGame() {
final String firstWord = game.newGame(); final String firstWord = game.newGame();
final String firstLetter = String.valueOf(firstWord.charAt(0)); final String firstLetter = String.valueOf(firstWord.charAt(0));
newWord(firstLetter); newWord(firstLetter);
repaint(); repaint();
} }
} }

View File

@ -1,25 +1,25 @@
package lingo.client.util; package lingo.client.util;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
/** /**
* Identifies a controller that will be created by an {@link FXMLLoader}. The * Identifies a controller that will be created by an {@link FXMLLoader}. The
* {@code FXMLLoader} will automatically inject {@code location} and * {@code FXMLLoader} will automatically inject {@code location} and
* {@code resources} properties into the controller, and then it will call the * {@code resources} properties into the controller, and then it will call the
* no-arg {@link #initialize()} method. This is the recommended approach: don't * no-arg {@link #initialize()} method. This is the recommended approach: don't
* use the {@link Initializable} interface. * use the {@link Initializable} interface.
*/ */
public interface FxmlController { public interface FxmlController {
/** /**
* Called by the {@link FXMLLoader} to initialize a controller after its * Called by the {@link FXMLLoader} to initialize a controller after its
* root element has been completely processed. This means all of the * root element has been completely processed. This means all of the
* controller's {@link FXML} elements will be injected, and they can be used * 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, * to wire up the GUI in ways that couldn't be accomplished using pure FXML,
* e.g. attaching property listeners. * e.g. attaching property listeners.
*/ */
void initialize(); void initialize();
} }

View File

@ -1,35 +1,35 @@
package lingo.client.view; package lingo.client.view;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
public abstract class Board { public abstract class Board {
public static final double HEIGHT = 300; public static final double HEIGHT = 300;
public static final double WIDTH = 250; public static final double WIDTH = 250;
public static final double SIDE = 50; public static final double SIDE = 50;
protected final Canvas canvas; protected final Canvas canvas;
protected final GraphicsContext gc; protected final GraphicsContext gc;
/** The leftmost x-coordinate */ /** The leftmost x-coordinate */
protected final double xInit; protected final double xInit;
/** The topmost y-coordinate */ /** The topmost y-coordinate */
protected final double yInit; protected final double yInit;
public Board(Canvas canvas, double xInit, double yInit) { public Board(Canvas canvas, double xInit, double yInit) {
this.canvas = canvas; this.canvas = canvas;
this.gc = canvas.getGraphicsContext2D(); this.gc = canvas.getGraphicsContext2D();
this.xInit = xInit; this.xInit = xInit;
this.yInit = yInit; this.yInit = yInit;
} }
public void clearBoard() { public void clearBoard() {
gc.clearRect(xInit, yInit, WIDTH, HEIGHT); gc.clearRect(xInit, yInit, WIDTH, HEIGHT);
} }
public abstract void drawBoard(); public abstract void drawBoard();
} }

View File

@ -1,80 +1,80 @@
package lingo.client.view; package lingo.client.view;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
public abstract class GameBoard extends Board { public abstract class GameBoard extends Board {
/** Tracks the player's previous guess evaluations */ /** Tracks the player's previous guess evaluations */
protected final List<int[]> results = new ArrayList<>(); protected final List<int[]> results = new ArrayList<>();
/** Tracks the player's score */ /** Tracks the player's score */
protected int score; protected int score;
public GameBoard(Canvas canvas, double xInit, double yInit) { public GameBoard(Canvas canvas, double xInit, double yInit) {
super(canvas, xInit, yInit); super(canvas, xInit, yInit);
} }
@Override @Override
public void clearBoard() { public void clearBoard() {
super.clearBoard(); super.clearBoard();
results.clear(); results.clear();
} }
protected void drawScore() { protected void drawScore() {
double scoreX = xInit + WIDTH / 2; double scoreX = xInit + WIDTH / 2;
double scoreY = yInit / 2; double scoreY = yInit / 2;
gc.setFill(Color.BLACK); gc.setFill(Color.BLACK);
gc.fillText(String.valueOf(score), scoreX, scoreY); gc.fillText(String.valueOf(score), scoreX, scoreY);
} }
protected void drawGrid() { protected void drawGrid() {
gc.beginPath(); gc.beginPath();
for (int x = 0; x <= WIDTH; x += SIDE) { for (int x = 0; x <= WIDTH; x += SIDE) {
gc.moveTo(xInit + x, yInit); gc.moveTo(xInit + x, yInit);
gc.lineTo(xInit + x, yInit + HEIGHT); gc.lineTo(xInit + x, yInit + HEIGHT);
} }
for (int y = 0; y <= HEIGHT; y += SIDE) { for (int y = 0; y <= HEIGHT; y += SIDE) {
gc.moveTo(xInit, yInit + y); gc.moveTo(xInit, yInit + y);
gc.lineTo(xInit + WIDTH, yInit + y); gc.lineTo(xInit + WIDTH, yInit + y);
} }
gc.setFill(Color.BLACK); gc.setFill(Color.BLACK);
gc.stroke(); gc.stroke();
} }
protected void drawResults() { protected void drawResults() {
double y = yInit + SIDE * 1.5; double y = yInit + SIDE * 1.5;
int numResults = Math.min(4, results.size()); int numResults = Math.min(4, results.size());
for (int i = 0; i < numResults; i++) { for (int i = 0; i < numResults; i++) {
double x = xInit + SIDE * 0.5; double x = xInit + SIDE * 0.5;
int[] result = results.get(results.size() - numResults + i); int[] result = results.get(results.size() - numResults + i);
for (int j = 0; j < 5; j++) { for (int j = 0; j < 5; j++) {
if (result[j] == 1) { if (result[j] == 1) {
gc.setFill(Color.YELLOW); gc.setFill(Color.YELLOW);
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
} else if (result[j] == 2) { } else if (result[j] == 2) {
gc.setFill(Color.ORANGE); gc.setFill(Color.ORANGE);
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE); gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
} }
x += SIDE; x += SIDE;
} }
y += SIDE; y += SIDE;
} }
} }
public void addResult(int[] value) { public void addResult(int[] value) {
results.add(value); results.add(value);
} }
public void addToScore(int value) { public void addToScore(int value) {
score += value; score += value;
} }
public void clearScore() { public void clearScore() {
score = 0; score = 0;
} }
} }

View File

@ -1,18 +1,18 @@
package lingo.client.view; package lingo.client.view;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
public class OpponentBoard extends GameBoard { public class OpponentBoard extends GameBoard {
public OpponentBoard(Canvas canvas, double xInit, double yInit) { public OpponentBoard(Canvas canvas, double xInit, double yInit) {
super(canvas, xInit, yInit); super(canvas, xInit, yInit);
} }
@Override @Override
public void drawBoard() { public void drawBoard() {
drawScore(); drawScore();
drawResults(); drawResults();
drawGrid(); drawGrid();
} }
} }

View File

@ -1,120 +1,120 @@
package lingo.client.view; package lingo.client.view;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
public class PlayerBoard extends GameBoard { public class PlayerBoard extends GameBoard {
/** Tracks the player's current guess */ /** Tracks the player's current guess */
private final StringBuilder guess = new StringBuilder(); private final StringBuilder guess = new StringBuilder();
/** Tracks the player's previous guesses */ /** Tracks the player's previous guesses */
private final List<String> guesses = new ArrayList<>(); private final List<String> guesses = new ArrayList<>();
/** Tracks the player's progress toward the current word */ /** Tracks the player's progress toward the current word */
private final Map<Integer, String> progress = new HashMap<>(); private final Map<Integer, String> progress = new HashMap<>();
public PlayerBoard(Canvas canvas, double xInit, double yInit) { public PlayerBoard(Canvas canvas, double xInit, double yInit) {
super(canvas, xInit, yInit); super(canvas, xInit, yInit);
} }
@Override @Override
public void clearBoard() { public void clearBoard() {
super.clearBoard(); super.clearBoard();
guess.setLength(0); guess.setLength(0);
guesses.clear(); guesses.clear();
progress.clear(); progress.clear();
results.clear(); results.clear();
} }
@Override @Override
public void drawBoard() { public void drawBoard() {
drawScore(); drawScore();
drawInput(); drawInput();
drawResults(); drawResults();
double yStart = drawGuesses(); double yStart = drawGuesses();
drawHint(yStart); drawHint(yStart);
drawGrid(); drawGrid();
} }
private void drawInput() { private void drawInput() {
gc.setFill(Color.GREEN); gc.setFill(Color.GREEN);
double x = xInit + SIDE * 0.5; double x = xInit + SIDE * 0.5;
double y = yInit + SIDE * 0.5; double y = yInit + SIDE * 0.5;
for (int i = 0; i < guess.length(); i++) { for (int i = 0; i < guess.length(); i++) {
String character = String.valueOf(guess.charAt(i)); String character = String.valueOf(guess.charAt(i));
gc.fillText(character, x, y); gc.fillText(character, x, y);
x += SIDE; x += SIDE;
} }
} }
private double drawGuesses() { private double drawGuesses() {
double y = yInit + SIDE * 1.5; double y = yInit + SIDE * 1.5;
double numGuesses = Math.min(4, guesses.size()); double numGuesses = Math.min(4, guesses.size());
for (int i = 0; i < numGuesses; i++) { for (int i = 0; i < numGuesses; i++) {
double x = xInit + SIDE * 0.5; double x = xInit + SIDE * 0.5;
String guess = guesses.get((int) (guesses.size() - numGuesses + i)); String guess = guesses.get((int) (guesses.size() - numGuesses + i));
for (int j = 0; j < 5; j++) { for (int j = 0; j < 5; j++) {
String character = String.valueOf(guess.charAt(j)); String character = String.valueOf(guess.charAt(j));
gc.setFill(Color.GREEN); gc.setFill(Color.GREEN);
gc.fillText(character, x, y); gc.fillText(character, x, y);
x += SIDE; x += SIDE;
} }
y += SIDE; y += SIDE;
} }
return y; return y;
} }
private void drawHint(double yStart) { private void drawHint(double yStart) {
double x = xInit + SIDE * 0.5; double x = xInit + SIDE * 0.5;
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
if (progress.containsKey(i)) { if (progress.containsKey(i)) {
gc.fillText(progress.get(i), x, yStart); gc.fillText(progress.get(i), x, yStart);
} }
x += SIDE; x += SIDE;
} }
} }
public void addGuess(String value) { public void addGuess(String value) {
guesses.add(value); guesses.add(value);
} }
public String getGuess() { public String getGuess() {
return guess.toString(); return guess.toString();
} }
public boolean handleBackspace() { public boolean handleBackspace() {
if (guess.length() > 0) { if (guess.length() > 0) {
guess.setLength(guess.length() - 1); guess.setLength(guess.length() - 1);
return true; return true;
} }
return false; return false;
} }
public String handleEnter() { public String handleEnter() {
if (guess.length() == 5) { if (guess.length() == 5) {
final String value = guess.toString(); final String value = guess.toString();
guess.setLength(0); guess.setLength(0);
return value; return value;
} }
return null; return null;
} }
public boolean handleLetter(String letter) { public boolean handleLetter(String letter) {
if (guess.length() < 5) { if (guess.length() < 5) {
guess.append(letter); guess.append(letter);
return true; return true;
} }
return false; return false;
} }
public void setProgress(int i, char letter) { public void setProgress(int i, char letter) {
progress.put(i, String.valueOf(letter)); progress.put(i, String.valueOf(letter));
} }
} }

View File

@ -1,195 +1,195 @@
h1 { h1 {
font: 24pt sans-serif; font: 24pt sans-serif;
font-variant: small-caps; font-variant: small-caps;
text-align: center text-align: center
} }
/* Below based on http://tobiasahlin.com/spinkit */ /* Below based on http://tobiasahlin.com/spinkit */
.sk-cube-grid { .sk-cube-grid {
width: 200px; width: 200px;
height: 200px; height: 200px;
margin: 35px auto; margin: 35px auto;
} }
.sk-cube-grid .sk-cube { .sk-cube-grid .sk-cube {
width: 20%; width: 20%;
height: 20%; height: 20%;
background-color: orange; background-color: orange;
float: left; float: left;
-webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; -webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out;
animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out;
} }
/* /*
0.0s - 26 0.0s - 26
0.1s - 27, 21 0.1s - 27, 21
0.2s - 28, 22, 16 0.2s - 28, 22, 16
0.3s - 29, 23, 17, 11 0.3s - 29, 23, 17, 11
0.4s - 30, 24, 18, 12, 06 0.4s - 30, 24, 18, 12, 06
0.5s - 25, 19, 13, 07, 01 0.5s - 25, 19, 13, 07, 01
0.6s - 20, 14, 08, 02 0.6s - 20, 14, 08, 02
0.7s - 15, 09, 03 0.7s - 15, 09, 03
0.8s - 10, 04 0.8s - 10, 04
0.9s - 05 0.9s - 05
*/ */
/* 0 second delay */ /* 0 second delay */
.sk-cube-grid .sk-cube26 { .sk-cube-grid .sk-cube26 {
-webkit-animation-delay: 0s; -webkit-animation-delay: 0s;
animation-delay: 0s; animation-delay: 0s;
} }
/* 0.1 second delay */ /* 0.1 second delay */
.sk-cube-grid .sk-cube27 { .sk-cube-grid .sk-cube27 {
-webkit-animation-delay: 0.1s; -webkit-animation-delay: 0.1s;
animation-delay: 0.1s; animation-delay: 0.1s;
} }
.sk-cube-grid .sk-cube21 { .sk-cube-grid .sk-cube21 {
-webkit-animation-delay: 0.1s; -webkit-animation-delay: 0.1s;
animation-delay: 0.1s; animation-delay: 0.1s;
} }
/* 0.2 second delay */ /* 0.2 second delay */
.sk-cube-grid .sk-cube28 { .sk-cube-grid .sk-cube28 {
-webkit-animation-delay: 0.2s; -webkit-animation-delay: 0.2s;
animation-delay: 0.2s; animation-delay: 0.2s;
} }
.sk-cube-grid .sk-cube22 { .sk-cube-grid .sk-cube22 {
-webkit-animation-delay: 0.2s; -webkit-animation-delay: 0.2s;
animation-delay: 0.2s; animation-delay: 0.2s;
} }
.sk-cube-grid .sk-cube16 { .sk-cube-grid .sk-cube16 {
-webkit-animation-delay: 0.2s; -webkit-animation-delay: 0.2s;
animation-delay: 0.2s; animation-delay: 0.2s;
} }
/* 0.3 second delay */ /* 0.3 second delay */
.sk-cube-grid .sk-cube29 { .sk-cube-grid .sk-cube29 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
.sk-cube-grid .sk-cube23 { .sk-cube-grid .sk-cube23 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
.sk-cube-grid .sk-cube17 { .sk-cube-grid .sk-cube17 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
.sk-cube-grid .sk-cube11 { .sk-cube-grid .sk-cube11 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
/* 0.4 second delay */ /* 0.4 second delay */
.sk-cube-grid .sk-cube30 { .sk-cube-grid .sk-cube30 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube24 { .sk-cube-grid .sk-cube24 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube18 { .sk-cube-grid .sk-cube18 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube12 { .sk-cube-grid .sk-cube12 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube6 { .sk-cube-grid .sk-cube6 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
/* 0.5 second delay */ /* 0.5 second delay */
.sk-cube-grid .sk-cube25 { .sk-cube-grid .sk-cube25 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube19 { .sk-cube-grid .sk-cube19 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube13 { .sk-cube-grid .sk-cube13 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube7 { .sk-cube-grid .sk-cube7 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube1 { .sk-cube-grid .sk-cube1 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
/* 0.6 second delay */ /* 0.6 second delay */
.sk-cube-grid .sk-cube20 { .sk-cube-grid .sk-cube20 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
.sk-cube-grid .sk-cube14 { .sk-cube-grid .sk-cube14 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
.sk-cube-grid .sk-cube8 { .sk-cube-grid .sk-cube8 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
.sk-cube-grid .sk-cube2 { .sk-cube-grid .sk-cube2 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
/* 0.7 second delay */ /* 0.7 second delay */
.sk-cube-grid .sk-cube15 { .sk-cube-grid .sk-cube15 {
-webkit-animation-delay: 0.7s; -webkit-animation-delay: 0.7s;
animation-delay: 0.7s; animation-delay: 0.7s;
} }
.sk-cube-grid .sk-cube9 { .sk-cube-grid .sk-cube9 {
-webkit-animation-delay: 0.7s; -webkit-animation-delay: 0.7s;
animation-delay: 0.7s; animation-delay: 0.7s;
} }
.sk-cube-grid .sk-cube3 { .sk-cube-grid .sk-cube3 {
-webkit-animation-delay: 0.7s; -webkit-animation-delay: 0.7s;
animation-delay: 0.7s; animation-delay: 0.7s;
} }
/* 0.8 second delay */ /* 0.8 second delay */
.sk-cube-grid .sk-cube10 { .sk-cube-grid .sk-cube10 {
-webkit-animation-delay: 0.8s; -webkit-animation-delay: 0.8s;
animation-delay: 0.8s; animation-delay: 0.8s;
} }
.sk-cube-grid .sk-cube4 { .sk-cube-grid .sk-cube4 {
-webkit-animation-delay: 0.8s; -webkit-animation-delay: 0.8s;
animation-delay: 0.8s; animation-delay: 0.8s;
} }
/* 0.9 second delay */ /* 0.9 second delay */
.sk-cube-grid .sk-cube5 { .sk-cube-grid .sk-cube5 {
-webkit-animation-delay: 0.9s; -webkit-animation-delay: 0.9s;
animation-delay: 0.9s; animation-delay: 0.9s;
} }
@-webkit-keyframes sk-cubeGridScaleDelay { @-webkit-keyframes sk-cubeGridScaleDelay {
0%, 70%, 100% { 0%, 70%, 100% {
-webkit-transform: scale3D(1, 1, 1); -webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1);
} 35% { } 35% {
-webkit-transform: scale3D(0, 0, 1); -webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1);
} }
} }
@keyframes sk-cubeGridScaleDelay { @keyframes sk-cubeGridScaleDelay {
0%, 70%, 100% { 0%, 70%, 100% {
-webkit-transform: scale3D(1, 1, 1); -webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1);
} 35% { } 35% {
-webkit-transform: scale3D(0, 0, 1); -webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1);
} }
} }

View File

@ -1,39 +1,39 @@
<!doctype html> <!doctype html>
<html> <html>
<body> <body>
<h1>Waiting for Opponent</h1> <h1>Waiting for Opponent</h1>
<!-- Based on http://tobiasahlin.com/spinkit --> <!-- Based on http://tobiasahlin.com/spinkit -->
<div class="sk-cube-grid"> <div class="sk-cube-grid">
<div class="sk-cube sk-cube1"></div> <div class="sk-cube sk-cube1"></div>
<div class="sk-cube sk-cube2"></div> <div class="sk-cube sk-cube2"></div>
<div class="sk-cube sk-cube3"></div> <div class="sk-cube sk-cube3"></div>
<div class="sk-cube sk-cube4"></div> <div class="sk-cube sk-cube4"></div>
<div class="sk-cube sk-cube5"></div> <div class="sk-cube sk-cube5"></div>
<div class="sk-cube sk-cube6"></div> <div class="sk-cube sk-cube6"></div>
<div class="sk-cube sk-cube7"></div> <div class="sk-cube sk-cube7"></div>
<div class="sk-cube sk-cube8"></div> <div class="sk-cube sk-cube8"></div>
<div class="sk-cube sk-cube9"></div> <div class="sk-cube sk-cube9"></div>
<div class="sk-cube sk-cube10"></div> <div class="sk-cube sk-cube10"></div>
<div class="sk-cube sk-cube11"></div> <div class="sk-cube sk-cube11"></div>
<div class="sk-cube sk-cube12"></div> <div class="sk-cube sk-cube12"></div>
<div class="sk-cube sk-cube13"></div> <div class="sk-cube sk-cube13"></div>
<div class="sk-cube sk-cube14"></div> <div class="sk-cube sk-cube14"></div>
<div class="sk-cube sk-cube15"></div> <div class="sk-cube sk-cube15"></div>
<div class="sk-cube sk-cube16"></div> <div class="sk-cube sk-cube16"></div>
<div class="sk-cube sk-cube17"></div> <div class="sk-cube sk-cube17"></div>
<div class="sk-cube sk-cube18"></div> <div class="sk-cube sk-cube18"></div>
<div class="sk-cube sk-cube19"></div> <div class="sk-cube sk-cube19"></div>
<div class="sk-cube sk-cube20"></div> <div class="sk-cube sk-cube20"></div>
<div class="sk-cube sk-cube21"></div> <div class="sk-cube sk-cube21"></div>
<div class="sk-cube sk-cube22"></div> <div class="sk-cube sk-cube22"></div>
<div class="sk-cube sk-cube23"></div> <div class="sk-cube sk-cube23"></div>
<div class="sk-cube sk-cube24"></div> <div class="sk-cube sk-cube24"></div>
<div class="sk-cube sk-cube25"></div> <div class="sk-cube sk-cube25"></div>
<div class="sk-cube sk-cube26"></div> <div class="sk-cube sk-cube26"></div>
<div class="sk-cube sk-cube27"></div> <div class="sk-cube sk-cube27"></div>
<div class="sk-cube sk-cube28"></div> <div class="sk-cube sk-cube28"></div>
<div class="sk-cube sk-cube29"></div> <div class="sk-cube sk-cube29"></div>
<div class="sk-cube sk-cube30"></div> <div class="sk-cube sk-cube30"></div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,38 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.geometry.Pos?> <?import javafx.geometry.Pos?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<BorderPane xmlns:fx="http://javafx.com/fxml" <BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="lingo.client.bootstrap.LingoPresenter" fx:controller="lingo.client.bootstrap.LingoPresenter"
fx:id="content" fx:id="content"
prefWidth="650" prefWidth="650"
prefHeight="420"> prefHeight="420">
<center> <center>
<BorderPane fx:id="gameModeChooser"> <BorderPane fx:id="gameModeChooser">
<center> <center>
<VBox spacing="20" alignment="CENTER"> <VBox spacing="20" alignment="CENTER">
<children> <children>
<Button text="Practice" onAction="#showSinglePlayer" prefWidth="350" styleClass="game-mode" /> <Button text="Practice" onAction="#showSinglePlayer" prefWidth="350" styleClass="game-mode" />
<Button text="Multiplayer" onAction="#showMultiplayer" prefWidth="350" styleClass="game-mode" /> <Button text="Multiplayer" onAction="#showMultiplayer" prefWidth="350" styleClass="game-mode" />
</children> </children>
</VBox> </VBox>
</center> </center>
<bottom> <bottom>
<Button text="Exit" onAction="#exit" prefWidth="50" styleClass="game-nav"> <Button text="Exit" onAction="#exit" prefWidth="50" styleClass="game-nav">
<BorderPane.alignment> <BorderPane.alignment>
<Pos fx:value="BOTTOM_LEFT" /> <Pos fx:value="BOTTOM_LEFT" />
</BorderPane.alignment> </BorderPane.alignment>
<BorderPane.margin> <BorderPane.margin>
<Insets bottom="10" left="10" /> <Insets bottom="10" left="10" />
</BorderPane.margin> </BorderPane.margin>
</Button> </Button>
</bottom> </bottom>
</BorderPane> </BorderPane>
</center> </center>
</BorderPane> </BorderPane>

View File

@ -1,27 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.geometry.Pos?> <?import javafx.geometry.Pos?>
<?import javafx.scene.canvas.Canvas?> <?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.StackPane?>
<?import javafx.scene.web.WebView?> <?import javafx.scene.web.WebView?>
<StackPane xmlns:fx="http://javafx.com/fxml" <StackPane xmlns:fx="http://javafx.com/fxml"
fx:controller="lingo.client.multiplayer.MultiplayerPresenter" fx:controller="lingo.client.multiplayer.MultiplayerPresenter"
fx:id="contentPane"> fx:id="contentPane">
<children> <children>
<Canvas fx:id="canvas" width="650" height="420" onKeyPressed="#keyPressed" /> <Canvas fx:id="canvas" width="650" height="420" onKeyPressed="#keyPressed" />
<WebView fx:id="webView" prefWidth="650" prefHeight="420" /> <WebView fx:id="webView" prefWidth="650" prefHeight="420" />
<Button fx:id="backButton" text="Back" prefWidth="50" styleClass="game-nav"> <Button fx:id="backButton" text="Back" prefWidth="50" styleClass="game-nav">
<StackPane.alignment> <StackPane.alignment>
<Pos fx:value="BOTTOM_LEFT" /> <Pos fx:value="BOTTOM_LEFT" />
</StackPane.alignment> </StackPane.alignment>
<StackPane.margin> <StackPane.margin>
<Insets bottom="10" left="10" /> <Insets bottom="10" left="10" />
</StackPane.margin> </StackPane.margin>
</Button> </Button>
</children> </children>
</StackPane> </StackPane>

View File

@ -1,12 +1,12 @@
.text { .text {
-fx-font-family: Helvetica; -fx-font-family: Helvetica;
-fx-font-smoothing-type: gray; -fx-font-smoothing-type: gray;
} }
.game-mode { .game-mode {
-fx-font-size: 24px; -fx-font-size: 24px;
} }
.game-nav { .game-nav {
-fx-font-size: 12px; -fx-font-size: 12px;
} }

View File

@ -1,31 +1,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.charego</groupId> <groupId>com.charego</groupId>
<artifactId>lingo-websocket</artifactId> <artifactId>lingo-websocket</artifactId>
<version>1.0</version> <version>1.0</version>
</parent> </parent>
<artifactId>lingo-websocket-common</artifactId> <artifactId>lingo-websocket-common</artifactId>
<name>Lingo WebSocket :: Common</name> <name>Lingo WebSocket :: Common</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>3.3.2</version> <version>3.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,32 +1,32 @@
package lingo.common; package lingo.common;
public class ChatMessage { public class ChatMessage {
private String username; private String username;
private String message; private String message;
public ChatMessage() {} public ChatMessage() {}
public ChatMessage(String username, String message) { public ChatMessage(String username, String message) {
this.username = username; this.username = username;
this.message = message; this.message = message;
} }
public String getUsername() { public String getUsername() {
return username; return username;
} }
public void setUsername(String username) { public void setUsername(String username) {
this.username = username; this.username = username;
} }
public String getMessage() { public String getMessage() {
return message; return message;
} }
public void setMessage(String message) { public void setMessage(String message) {
this.message = message; this.message = message;
} }
} }

View File

@ -1,137 +1,137 @@
package lingo.common; package lingo.common;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class Game { public class Game {
public static final int INCORRECT_CHARACTER = 0; public static final int INCORRECT_CHARACTER = 0;
public static final int INCORRECT_POSITION = 1; public static final int INCORRECT_POSITION = 1;
public static final int CORRECT_CHARACTER = 2; public static final int CORRECT_CHARACTER = 2;
public static final int WORD_LENGTH = 5; public static final int WORD_LENGTH = 5;
/** Nein nein nein nein nein! */ /** Nein nein nein nein nein! */
private static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 }; private static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 };
private static final AtomicInteger idCounter = new AtomicInteger(0); private static final AtomicInteger idCounter = new AtomicInteger(0);
private int id; private int id;
private Player playerOne; private Player playerOne;
private Player playerTwo; private Player playerTwo;
private Set<String> acceptableGuesses; private Set<String> acceptableGuesses;
private List<String> possibleWords; private List<String> possibleWords;
private String word; private String word;
private int wordIndex = 0; private int wordIndex = 0;
public Game() { public Game() {
// Empty constructor required for serialization // Empty constructor required for serialization
} }
public Game(Player host) { public Game(Player host) {
this.id = idCounter.incrementAndGet(); this.id = idCounter.incrementAndGet();
this.playerOne = host; this.playerOne = host;
} }
private static int indexOf(char[] array, char searchTerm) { private static int indexOf(char[] array, char searchTerm) {
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < WORD_LENGTH; i++) {
if (array[i] == searchTerm) { if (array[i] == searchTerm) {
return i; return i;
} }
} }
return -1; return -1;
} }
public static boolean isCorrect(int[] result) { public static boolean isCorrect(int[] result) {
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < WORD_LENGTH; i++) {
if (result[i] != CORRECT_CHARACTER) { if (result[i] != CORRECT_CHARACTER) {
return false; return false;
} }
} }
return true; return true;
} }
public static boolean isInvalid(int[] result) { public static boolean isInvalid(int[] result) {
return Arrays.equals(result, INVALID_GUESS); return Arrays.equals(result, INVALID_GUESS);
} }
public int[] evaluate(String guess) { public int[] evaluate(String guess) {
if (!acceptableGuesses.contains(guess)) { if (!acceptableGuesses.contains(guess)) {
return INVALID_GUESS; return INVALID_GUESS;
} }
// the guess is acceptable // the guess is acceptable
int[] result = new int[WORD_LENGTH]; int[] result = new int[WORD_LENGTH];
char[] remaining = new char[WORD_LENGTH]; char[] remaining = new char[WORD_LENGTH];
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < WORD_LENGTH; i++) {
if (guess.charAt(i) == word.charAt(i)) { if (guess.charAt(i) == word.charAt(i)) {
result[i] = CORRECT_CHARACTER; result[i] = CORRECT_CHARACTER;
} else { } else {
result[i] = INCORRECT_CHARACTER; result[i] = INCORRECT_CHARACTER;
remaining[i] = word.charAt(i); remaining[i] = word.charAt(i);
} }
} }
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < WORD_LENGTH; i++) {
if (result[i] == INCORRECT_CHARACTER) { if (result[i] == INCORRECT_CHARACTER) {
int index = indexOf(remaining, guess.charAt(i)); int index = indexOf(remaining, guess.charAt(i));
if (index != -1) { if (index != -1) {
result[i] = INCORRECT_POSITION; result[i] = INCORRECT_POSITION;
remaining[index] = 0; remaining[index] = 0;
} }
} }
} }
return result; return result;
} }
public int getId() { public int getId() {
return id; return id;
} }
public Player getPlayerOne() { public Player getPlayerOne() {
return playerOne; return playerOne;
} }
public Player getPlayerTwo() { public Player getPlayerTwo() {
return playerTwo; return playerTwo;
} }
public String newGame() { public String newGame() {
Collections.shuffle(possibleWords); Collections.shuffle(possibleWords);
wordIndex = 0; wordIndex = 0;
return newWord(); return newWord();
} }
public String newWord() { public String newWord() {
word = possibleWords.get(wordIndex++); word = possibleWords.get(wordIndex++);
return word; return word;
} }
public void setAcceptableGuesses(Set<String> value) { public void setAcceptableGuesses(Set<String> value) {
this.acceptableGuesses = value; this.acceptableGuesses = value;
} }
public void setId(int value) { public void setId(int value) {
this.id = value; this.id = value;
} }
public void setPlayerOne(Player value) { public void setPlayerOne(Player value) {
this.playerOne = value; this.playerOne = value;
} }
public void setPlayerTwo(Player value) { public void setPlayerTwo(Player value) {
this.playerTwo = value; this.playerTwo = value;
} }
public void setPossibleWords(List<String> value) { public void setPossibleWords(List<String> value) {
this.possibleWords = value; this.possibleWords = value;
} }
} }

View File

@ -1,32 +1,32 @@
package lingo.common; package lingo.common;
public class GameLeftMessage { public class GameLeftMessage {
private Game game; private Game game;
private Player gameLeaver; private Player gameLeaver;
public GameLeftMessage() {} public GameLeftMessage() {}
public GameLeftMessage(Game game, Player gameLeaver) { public GameLeftMessage(Game game, Player gameLeaver) {
this.game = game; this.game = game;
this.gameLeaver = gameLeaver; this.gameLeaver = gameLeaver;
} }
public Game getGame() { public Game getGame() {
return game; return game;
} }
public Player getGameLeaver() { public Player getGameLeaver() {
return gameLeaver; return gameLeaver;
} }
public void setGame(Game game) { public void setGame(Game game) {
this.game = game; this.game = game;
} }
public void setGameLeaver(Player gameLeaver) { public void setGameLeaver(Player gameLeaver) {
this.gameLeaver = gameLeaver; this.gameLeaver = gameLeaver;
} }
} }

View File

@ -1,40 +1,40 @@
package lingo.common; package lingo.common;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
public class Player { public class Player {
@JsonIgnore @JsonIgnore
private String sessionId; private String sessionId;
private String username; private String username;
public Player() { public Player() {
// Empty constructor required for serialization // Empty constructor required for serialization
} }
public Player(String sessionId) { public Player(String sessionId) {
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public String getSessionId() { public String getSessionId() {
return sessionId; return sessionId;
} }
public String getUsername() { public String getUsername() {
return username; return username;
} }
public void setUsername(String value) { public void setUsername(String value) {
this.username = value; this.username = value;
} }
@Override @Override
public String toString() { public String toString() {
if (username != null) { if (username != null) {
return username; return username;
} }
return sessionId; return sessionId;
} }
} }

View File

@ -1,47 +1,47 @@
package lingo.common; package lingo.common;
public class Report { public class Report {
private boolean correct; private boolean correct;
private String firstLetter; private String firstLetter;
private String guess; private String guess;
private int[] result; private int[] result;
public Report() {} public Report() {}
public String getFirstLetter() { public String getFirstLetter() {
return firstLetter; return firstLetter;
} }
public void setFirstLetter(String value) { public void setFirstLetter(String value) {
this.firstLetter = value; this.firstLetter = value;
} }
public String getGuess() { public String getGuess() {
return guess; return guess;
} }
public void setGuess(String value) { public void setGuess(String value) {
this.guess = value; this.guess = value;
} }
public int[] getResult() { public int[] getResult() {
return result; return result;
} }
public void setResult(int[] value) { public void setResult(int[] value) {
this.result = value; this.result = value;
} }
public boolean isCorrect() { public boolean isCorrect() {
return correct; return correct;
} }
public void setCorrect(boolean value) { public void setCorrect(boolean value) {
this.correct = value; this.correct = value;
} }
} }

View File

@ -1,43 +1,43 @@
package lingo.common; package lingo.common;
public class SetUsernameMessage { public class SetUsernameMessage {
private String errorMessage; private String errorMessage;
private boolean success; private boolean success;
private String username; private String username;
public SetUsernameMessage() {} public SetUsernameMessage() {}
public SetUsernameMessage(boolean success, String username, String errorMessage) { public SetUsernameMessage(boolean success, String username, String errorMessage) {
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
this.success = success; this.success = success;
this.username = username; this.username = username;
} }
public String getErrorMessage() { public String getErrorMessage() {
return errorMessage; return errorMessage;
} }
public boolean isSuccess() { public boolean isSuccess() {
return success; return success;
} }
public String getUsername() { public String getUsername() {
return username; return username;
} }
public void setErrorMessage(String errorMessage) { public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
} }
public void setSuccess(boolean success) { public void setSuccess(boolean success) {
this.success = success; this.success = success;
} }
public void setUsername(String username) { public void setUsername(String username) {
this.username = username; this.username = username;
} }
} }

View File

@ -1,38 +1,38 @@
package lingo.common; package lingo.common;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
public class WordReader { public class WordReader {
private static void readFileToCollection(String filename, Collection<String> c) throws IOException { private static void readFileToCollection(String filename, Collection<String> c) throws IOException {
try (final InputStream stream = WordReader.class.getResourceAsStream(filename); try (final InputStream stream = WordReader.class.getResourceAsStream(filename);
final InputStreamReader streamReader = new InputStreamReader(stream); final InputStreamReader streamReader = new InputStreamReader(stream);
final BufferedReader bufferedReader = new BufferedReader(streamReader)) { final BufferedReader bufferedReader = new BufferedReader(streamReader)) {
String line = null; String line = null;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
c.add(line); c.add(line);
} }
} }
} }
public static List<String> readFileToList(String filename) throws IOException { public static List<String> readFileToList(String filename) throws IOException {
final List<String> list = new ArrayList<>(); final List<String> list = new ArrayList<>();
readFileToCollection(filename, list); readFileToCollection(filename, list);
return list; return list;
} }
public static Set<String> readFileToSet(String filename) throws IOException { public static Set<String> readFileToSet(String filename) throws IOException {
final Set<String> list = new HashSet<>(); final Set<String> list = new HashSet<>();
readFileToCollection(filename, list); readFileToCollection(filename, list);
return list; return list;
} }
} }

80
pom.xml
View File

@ -1,40 +1,40 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version> <version>1.5.10.RELEASE</version>
</parent> </parent>
<groupId>com.charego</groupId> <groupId>com.charego</groupId>
<artifactId>lingo-websocket</artifactId> <artifactId>lingo-websocket</artifactId>
<version>1.0</version> <version>1.0</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Lingo WebSocket</name> <name>Lingo WebSocket</name>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
</properties> </properties>
<modules> <modules>
<module>client-api</module> <module>client-api</module>
<module>common</module> <module>common</module>
<module>server</module> <module>server</module>
</modules> </modules>
<profiles> <profiles>
<!-- Prevent client module from building on Heroku --> <!-- Prevent client module from building on Heroku -->
<!-- Heroku's JDKs are headless --> <!-- Heroku's JDKs are headless -->
<profile> <profile>
<id>javafx</id> <id>javafx</id>
<modules> <modules>
<module>client</module> <module>client</module>
</modules> </modules>
</profile> </profile>
</profiles> </profiles>
</project> </project>

View File

@ -1,64 +1,64 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.charego</groupId> <groupId>com.charego</groupId>
<artifactId>lingo-websocket</artifactId> <artifactId>lingo-websocket</artifactId>
<version>1.0</version> <version>1.0</version>
</parent> </parent>
<artifactId>lingo-websocket-server</artifactId> <artifactId>lingo-websocket-server</artifactId>
<name>Lingo WebSocket :: Server</name> <name>Lingo WebSocket :: Server</name>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<version>${project.version}</version> <version>${project.version}</version>
<artifactId>lingo-websocket-client-api</artifactId> <artifactId>lingo-websocket-client-api</artifactId>
</dependency> </dependency>
<!-- Web --> <!-- Web -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<!-- Development Tools --> <!-- Development Tools -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<!-- Prevent transitive application to other modules --> <!-- Prevent transitive application to other modules -->
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration> <configuration>
<!-- Enable hot refreshing of resources --> <!-- Enable hot refreshing of resources -->
<!-- Add src/main/resources to the classpath --> <!-- Add src/main/resources to the classpath -->
<!-- Remove duplicate resources from target/classes --> <!-- Remove duplicate resources from target/classes -->
<addResources>true</addResources> <addResources>true</addResources>
<!-- Add src/main/config to the classpath --> <!-- Add src/main/config to the classpath -->
<folders>src/main/config</folders> <folders>src/main/config</folders>
</configuration> </configuration>
<executions> <executions>
<!-- Repackage as executable JAR (java -jar) --> <!-- Repackage as executable JAR (java -jar) -->
<execution> <execution>
<goals> <goals>
<goal>repackage</goal> <goal>repackage</goal>
</goals> </goals>
<configuration> <configuration>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -1,20 +1,20 @@
package lingo.server; package lingo.server;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class LingoServer { public class LingoServer {
public static void main(String[] args) { public static void main(String[] args) {
// Heroku dynamically assigns a port // Heroku dynamically assigns a port
final String webPort = System.getenv("PORT"); final String webPort = System.getenv("PORT");
if (webPort != null && !webPort.isEmpty()) { if (webPort != null && !webPort.isEmpty()) {
System.setProperty("server.port", webPort); System.setProperty("server.port", webPort);
} }
SpringApplication.run(LingoServer.class, args); SpringApplication.run(LingoServer.class, args);
} }
} }

View File

@ -1,85 +1,85 @@
package lingo.server; package lingo.server;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.AbstractSubProtocolEvent; import org.springframework.web.socket.messaging.AbstractSubProtocolEvent;
import org.springframework.web.socket.messaging.SessionConnectedEvent; import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent; import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import lingo.common.Player; import lingo.common.Player;
@Component @Component
public class SessionManager implements ApplicationListener<AbstractSubProtocolEvent> { public class SessionManager implements ApplicationListener<AbstractSubProtocolEvent> {
public interface Listener { public interface Listener {
void playerJoined(Player player); void playerJoined(Player player);
void playerLeft(Player player); void playerLeft(Player player);
} }
private static final Logger log = LoggerFactory.getLogger(SessionManager.class); private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
private final Map<String, Player> playerBySession = new HashMap<>(); private final Map<String, Player> playerBySession = new HashMap<>();
private final Set<Listener> listeners = new HashSet<>(); private final Set<Listener> listeners = new HashSet<>();
public void addListener(Listener listener) { public void addListener(Listener listener) {
synchronized (listeners) { synchronized (listeners) {
listeners.add(listener); listeners.add(listener);
} }
} }
public Player getPlayer(String sessionId) { public Player getPlayer(String sessionId) {
return playerBySession.get(sessionId); return playerBySession.get(sessionId);
} }
public int getPlayerCount() { public int getPlayerCount() {
return playerBySession.size(); return playerBySession.size();
} }
@Override @Override
public void onApplicationEvent(AbstractSubProtocolEvent event) { public void onApplicationEvent(AbstractSubProtocolEvent event) {
if (event instanceof SessionConnectedEvent) { if (event instanceof SessionConnectedEvent) {
onSessionConnected((SessionConnectedEvent) event); onSessionConnected((SessionConnectedEvent) event);
} else if (event instanceof SessionDisconnectEvent) { } else if (event instanceof SessionDisconnectEvent) {
onSessionDisconnect((SessionDisconnectEvent) event); onSessionDisconnect((SessionDisconnectEvent) event);
} }
} }
private void onSessionConnected(SessionConnectedEvent event) { private void onSessionConnected(SessionConnectedEvent event) {
final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId(); final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId();
final Player player = new Player(sessionId); final Player player = new Player(sessionId);
log.info("Player connected: {}", player); log.info("Player connected: {}", player);
playerBySession.put(sessionId, player); playerBySession.put(sessionId, player);
synchronized (listeners) { synchronized (listeners) {
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.playerJoined(player); listener.playerJoined(player);
} }
} }
} }
private void onSessionDisconnect(SessionDisconnectEvent event) { private void onSessionDisconnect(SessionDisconnectEvent event) {
final String sessionId = event.getSessionId(); final String sessionId = event.getSessionId();
final Player player = playerBySession.remove(sessionId); final Player player = playerBySession.remove(sessionId);
log.info("Player disconnected: {}", player); log.info("Player disconnected: {}", player);
synchronized (listeners) { synchronized (listeners) {
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.playerLeft(player); listener.playerLeft(player);
} }
} }
} }
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
synchronized (listeners) { synchronized (listeners) {
listeners.remove(listener); listeners.remove(listener);
} }
} }
} }

View File

@ -1,39 +1,39 @@
package lingo.server; package lingo.server;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lingo.common.WordReader; import lingo.common.WordReader;
@Component @Component
public class WordRepository { public class WordRepository {
private final Set<String> guesses; private final Set<String> guesses;
private final List<String> words; private final List<String> words;
public WordRepository() throws IOException { public WordRepository() throws IOException {
guesses = WordReader.readFileToSet("/guesses.txt"); guesses = WordReader.readFileToSet("/guesses.txt");
words = WordReader.readFileToList("/words.txt"); words = WordReader.readFileToList("/words.txt");
} }
/** /**
* Returns the set of acceptable guesses (unmodifiable). * Returns the set of acceptable guesses (unmodifiable).
*/ */
public Set<String> getGuesses() { public Set<String> getGuesses() {
return Collections.unmodifiableSet(guesses); return Collections.unmodifiableSet(guesses);
} }
/** /**
* Returns a copy of the list of potential answers (OK to shuffle it). * Returns a copy of the list of potential answers (OK to shuffle it).
*/ */
public List<String> getWords() { public List<String> getWords() {
return new ArrayList<>(words); return new ArrayList<>(words);
} }
} }

View File

@ -1,200 +1,200 @@
/* Based on http://tobiasahlin.com/spinkit */ /* Based on http://tobiasahlin.com/spinkit */
.sk-cube-grid { .sk-cube-grid {
width: 200px; /* Should be 5x the width below */ width: 200px; /* Should be 5x the width below */
height: 240px; /* Should be 6x the height below */ height: 240px; /* Should be 6x the height below */
margin: 0 auto; margin: 0 auto;
} }
.sk-cube-grid .sk-cube { .sk-cube-grid .sk-cube {
width: 40px; width: 40px;
height: 40px; height: 40px;
float: left; float: left;
-webkit-animation: sk-cubeGridScaleDelay 2s infinite ease-in-out; -webkit-animation: sk-cubeGridScaleDelay 2s infinite ease-in-out;
animation: sk-cubeGridScaleDelay 2s infinite ease-in-out; animation: sk-cubeGridScaleDelay 2s infinite ease-in-out;
} }
/* /*
0.0s - 26 0.0s - 26
0.1s - 27, 21 0.1s - 27, 21
0.2s - 28, 22, 16 0.2s - 28, 22, 16
0.3s - 29, 23, 17, 11 0.3s - 29, 23, 17, 11
0.4s - 30, 24, 18, 12, 06 0.4s - 30, 24, 18, 12, 06
0.5s - 25, 19, 13, 07, 01 0.5s - 25, 19, 13, 07, 01
0.6s - 20, 14, 08, 02 0.6s - 20, 14, 08, 02
0.7s - 15, 09, 03 0.7s - 15, 09, 03
0.8s - 10, 04 0.8s - 10, 04
0.9s - 05 0.9s - 05
*/ */
/* 0 second delay */ /* 0 second delay */
.sk-cube-grid .sk-cube26 { .sk-cube-grid .sk-cube26 {
-webkit-animation-delay: 0s; -webkit-animation-delay: 0s;
animation-delay: 0s; animation-delay: 0s;
} }
/* 0.1 second delay */ /* 0.1 second delay */
.sk-cube-grid .sk-cube27 { .sk-cube-grid .sk-cube27 {
-webkit-animation-delay: 0.1s; -webkit-animation-delay: 0.1s;
animation-delay: 0.1s; animation-delay: 0.1s;
} }
.sk-cube-grid .sk-cube21 { .sk-cube-grid .sk-cube21 {
-webkit-animation-delay: 0.1s; -webkit-animation-delay: 0.1s;
animation-delay: 0.1s; animation-delay: 0.1s;
} }
/* 0.2 second delay */ /* 0.2 second delay */
.sk-cube-grid .sk-cube28 { .sk-cube-grid .sk-cube28 {
-webkit-animation-delay: 0.2s; -webkit-animation-delay: 0.2s;
animation-delay: 0.2s; animation-delay: 0.2s;
} }
.sk-cube-grid .sk-cube22 { .sk-cube-grid .sk-cube22 {
-webkit-animation-delay: 0.2s; -webkit-animation-delay: 0.2s;
animation-delay: 0.2s; animation-delay: 0.2s;
} }
.sk-cube-grid .sk-cube16 { .sk-cube-grid .sk-cube16 {
-webkit-animation-delay: 0.2s; -webkit-animation-delay: 0.2s;
animation-delay: 0.2s; animation-delay: 0.2s;
} }
/* 0.3 second delay */ /* 0.3 second delay */
.sk-cube-grid .sk-cube29 { .sk-cube-grid .sk-cube29 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
.sk-cube-grid .sk-cube23 { .sk-cube-grid .sk-cube23 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
.sk-cube-grid .sk-cube17 { .sk-cube-grid .sk-cube17 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
.sk-cube-grid .sk-cube11 { .sk-cube-grid .sk-cube11 {
-webkit-animation-delay: 0.3s; -webkit-animation-delay: 0.3s;
animation-delay: 0.3s; animation-delay: 0.3s;
} }
/* 0.4 second delay */ /* 0.4 second delay */
.sk-cube-grid .sk-cube30 { .sk-cube-grid .sk-cube30 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube24 { .sk-cube-grid .sk-cube24 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube18 { .sk-cube-grid .sk-cube18 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube12 { .sk-cube-grid .sk-cube12 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
.sk-cube-grid .sk-cube6 { .sk-cube-grid .sk-cube6 {
-webkit-animation-delay: 0.4s; -webkit-animation-delay: 0.4s;
animation-delay: 0.4s; animation-delay: 0.4s;
} }
/* 0.5 second delay */ /* 0.5 second delay */
.sk-cube-grid .sk-cube25 { .sk-cube-grid .sk-cube25 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube19 { .sk-cube-grid .sk-cube19 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube13 { .sk-cube-grid .sk-cube13 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube7 { .sk-cube-grid .sk-cube7 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
.sk-cube-grid .sk-cube1 { .sk-cube-grid .sk-cube1 {
-webkit-animation-delay: 0.5s; -webkit-animation-delay: 0.5s;
animation-delay: 0.5s; animation-delay: 0.5s;
} }
/* 0.6 second delay */ /* 0.6 second delay */
.sk-cube-grid .sk-cube20 { .sk-cube-grid .sk-cube20 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
.sk-cube-grid .sk-cube14 { .sk-cube-grid .sk-cube14 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
.sk-cube-grid .sk-cube8 { .sk-cube-grid .sk-cube8 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
.sk-cube-grid .sk-cube2 { .sk-cube-grid .sk-cube2 {
-webkit-animation-delay: 0.6s; -webkit-animation-delay: 0.6s;
animation-delay: 0.6s; animation-delay: 0.6s;
} }
/* 0.7 second delay */ /* 0.7 second delay */
.sk-cube-grid .sk-cube15 { .sk-cube-grid .sk-cube15 {
-webkit-animation-delay: 0.7s; -webkit-animation-delay: 0.7s;
animation-delay: 0.7s; animation-delay: 0.7s;
} }
.sk-cube-grid .sk-cube9 { .sk-cube-grid .sk-cube9 {
-webkit-animation-delay: 0.7s; -webkit-animation-delay: 0.7s;
animation-delay: 0.7s; animation-delay: 0.7s;
} }
.sk-cube-grid .sk-cube3 { .sk-cube-grid .sk-cube3 {
-webkit-animation-delay: 0.7s; -webkit-animation-delay: 0.7s;
animation-delay: 0.7s; animation-delay: 0.7s;
} }
/* 0.8 second delay */ /* 0.8 second delay */
.sk-cube-grid .sk-cube10 { .sk-cube-grid .sk-cube10 {
-webkit-animation-delay: 0.8s; -webkit-animation-delay: 0.8s;
animation-delay: 0.8s; animation-delay: 0.8s;
} }
.sk-cube-grid .sk-cube4 { .sk-cube-grid .sk-cube4 {
-webkit-animation-delay: 0.8s; -webkit-animation-delay: 0.8s;
animation-delay: 0.8s; animation-delay: 0.8s;
} }
/* 0.9 second delay */ /* 0.9 second delay */
.sk-cube-grid .sk-cube5 { .sk-cube-grid .sk-cube5 {
-webkit-animation-delay: 0.9s; -webkit-animation-delay: 0.9s;
animation-delay: 0.9s; animation-delay: 0.9s;
} }
@-webkit-keyframes sk-cubeGridScaleDelay { @-webkit-keyframes sk-cubeGridScaleDelay {
0%, 100% { 0%, 100% {
background-color: orange; background-color: orange;
-webkit-transform: scale3D(1, 1, 1); -webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1);
} 70% { } 70% {
background-color: yellow; background-color: yellow;
-webkit-transform: scale3D(1, 1, 1); -webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1);
} 35% { } 35% {
background-color: greenyellow; background-color: greenyellow;
-webkit-transform: scale3D(0, 0, 1); -webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1);
} }
} }
@keyframes sk-cubeGridScaleDelay { @keyframes sk-cubeGridScaleDelay {
0%, 100% { 0%, 100% {
background-color: orange; background-color: orange;
-webkit-transform: scale3D(1, 1, 1); -webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1);
} 70% { } 70% {
background-color: yellow; background-color: yellow;
-webkit-transform: scale3D(1, 1, 1); -webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1);
} 35% { } 35% {
background-color: greenyellow; background-color: greenyellow;
-webkit-transform: scale3D(0, 0, 1); -webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1);
} }
} }

View File

@ -1,71 +1,71 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Lingo</title> <title>Lingo</title>
<link rel="stylesheet" href="layout.css"> <link rel="stylesheet" href="layout.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
</head> </head>
<body> <body>
<div id="vue-app" v-cloak> <div id="vue-app" v-cloak>
<div class="main column"> <div class="main column">
<div class="header row">Lingo</div> <div class="header row">Lingo</div>
<div class="body row nofooter"> <div class="body row nofooter">
<div v-show="!username" class="form"> <div v-show="!username" class="form">
<h2>What is your name?</h2> <h2>What is your name?</h2>
<input id="nicknameInput" type="text" class="form-control" maxlength="16" autofocus> <input id="nicknameInput" type="text" class="form-control" maxlength="16" autofocus>
<p class="error-message">{{ usernameError }}</p> <p class="error-message">{{ usernameError }}</p>
</div> </div>
<div v-show="username"> <div v-show="username">
<div v-bind:class="{ primary: !inStartedGame }" class="lobby column"> <div v-bind:class="{ primary: !inStartedGame }" class="lobby column">
<div class="body row noheader nofooter scroll-y"> <div class="body row noheader nofooter scroll-y">
<div class="panel"> <div class="panel">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">Games</h3> <h3 class="panel-title">Games</h3>
</div> </div>
<div class="list-group"> <div class="list-group">
<div v-if="games.length === 0" class="list-group-item">There are no games</div> <div v-if="games.length === 0" class="list-group-item">There are no games</div>
<template v-for="game in games"> <template v-for="game in games">
<div v-if="game.started" v-bind:id="'game-' + game.id" class="list-group-item"> <div v-if="game.started" v-bind:id="'game-' + game.id" class="list-group-item">
<strong>{{ game.playerOne }}</strong> vs. <strong>{{ game.playerTwo }}</strong> <strong>{{ game.playerOne }}</strong> vs. <strong>{{ game.playerTwo }}</strong>
</div> </div>
<button v-else v-bind:id="'game-' + game.id" @click="joinGame" type="button" v-bind:disabled="inGame" class="list-group-item"> <button v-else v-bind:id="'game-' + game.id" @click="joinGame" type="button" v-bind:disabled="inGame" class="list-group-item">
<strong>{{ game.playerOne }}</strong> wants to play <strong>{{ game.playerOne }}</strong> wants to play
</button> </button>
</template> </template>
</div> </div>
</div> </div>
<button v-show="inGame" @click="leaveGame" type="button" class="leave button">Leave game</button> <button v-show="inGame" @click="leaveGame" type="button" class="leave button">Leave game</button>
<button v-show="!inGame" @click="hostGame" type="button" class="create button">Create game</button> <button v-show="!inGame" @click="hostGame" type="button" class="create button">Create game</button>
</div> </div>
</div> </div>
<div v-bind:class="{ primary: inStartedGame }" class="game column"> <div v-bind:class="{ primary: inStartedGame }" class="game column">
<div class="body row noheader nofooter"> <div class="body row noheader nofooter">
<div v-show="inStartedGame" @keydown="onCanvasKeydown" @keypress="onCanvasKeypress"> <div v-show="inStartedGame" @keydown="onCanvasKeydown" @keypress="onCanvasKeypress">
<canvas id="canvas" class="centered" width="600" height="475" tabindex="1"></canvas> <canvas id="canvas" class="centered" width="600" height="475" tabindex="1"></canvas>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="chat column"> <div class="chat column">
<div class="header row">Chat</div> <div class="header row">Chat</div>
<div v-autoscroll class="body row scroll-y"> <div v-autoscroll class="body row scroll-y">
<div v-for="message in messages" class="message-item" :class="{ log: !message.sender }"> <div v-for="message in messages" class="message-item" :class="{ log: !message.sender }">
<strong v-if="message.sender">{{ message.sender }}</strong> <strong v-if="message.sender">{{ message.sender }}</strong>
<span>{{ message.body }}</span> <span>{{ message.body }}</span>
</div> </div>
</div> </div>
<div class="footer row"> <div class="footer row">
<textarea @keypress="onChatKeypress" placeholder="Send a message" tabindex="2"></textarea> <textarea @keypress="onChatKeypress" placeholder="Send a message" tabindex="2"></textarea>
</div> </div>
</div> </div>
</div> </div>
<!-- https://github.com/stomp-js/stomp-websocket --> <!-- https://github.com/stomp-js/stomp-websocket -->
<script src="stomp-3.1.1.min.js"></script> <script src="stomp-3.1.1.min.js"></script>
<script src="client.js"></script> <script src="client.js"></script>
</body> </body>
</html> </html>

View File

@ -1,98 +1,98 @@
/* Generic layout rules */ /* Generic layout rules */
body { body {
margin: 0; margin: 0;
} }
.row, .column { .row, .column {
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
} }
.row { .row {
left: 0; left: 0;
right: 0; right: 0;
} }
.column { .column {
top: 0; top: 0;
bottom: 0; bottom: 0;
} }
.scroll-x { .scroll-x {
overflow-x: auto; overflow-x: auto;
} }
.scroll-y { .scroll-y {
overflow-y: auto; overflow-y: auto;
} }
/* Specific layout rules */ /* Specific layout rules */
.main.column { .main.column {
left: 0; left: 0;
right: 300px; right: 300px;
} }
.chat.column { .chat.column {
width: 300px; width: 300px;
right: 0; right: 0;
} }
.header.row { .header.row {
height: 75px; height: 75px;
line-height: 75px; line-height: 75px;
} }
.body.row { .body.row {
top: 75px; top: 75px;
bottom: 66px; bottom: 66px;
} }
.body.row.nofooter { .body.row.nofooter {
bottom: 0; bottom: 0;
} }
.body.row.noheader { .body.row.noheader {
top: 0; top: 0;
} }
.footer.row { .footer.row {
height: 66px; height: 66px;
bottom: 0; bottom: 0;
} }
.chat.column { .chat.column {
border-left: 1px solid black; border-left: 1px solid black;
} }
.chat.column .body { .chat.column .body {
padding: 0 10px; padding: 0 10px;
} }
.chat.column .footer { .chat.column .footer {
border-top: 1px solid black; border-top: 1px solid black;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;
padding-bottom: 5px; padding-bottom: 5px;
} }
.lobby.column { .lobby.column {
width: 300px; width: 300px;
left: 0; left: 0;
} }
.lobby.column .body { .lobby.column .body {
padding: 0 10px; padding: 0 10px;
} }
.lobby.column.primary { .lobby.column.primary {
width: inherit; width: inherit;
right: 0; right: 0;
} }
.game.column.primary { .game.column.primary {
left: 300px; left: 300px;
right: 0; right: 0;
border-left: 1px solid black; border-left: 1px solid black;
} }

View File

@ -1,24 +1,24 @@
canvas { canvas {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
} }
#skipDiv { #skipDiv {
margin-top: 20px; margin-top: 20px;
text-align: center text-align: center
} }
#skipButton { #skipButton {
width: 200px; width: 200px;
font-size: 28px; font-size: 28px;
font-variant: small-caps; font-variant: small-caps;
background-color: black; background-color: black;
color: white; color: white;
cursor: pointer; cursor: pointer;
padding: 10px; padding: 10px;
border: none; border: none;
} }
.hidden { .hidden {
display: none; display: none;
} }

View File

@ -1,18 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Lingo</title> <title>Lingo</title>
<link rel="stylesheet" href="practice.css"> <link rel="stylesheet" href="practice.css">
</head> </head>
<body> <body>
<canvas id="canvas" width="300" height="400"></canvas> <canvas id="canvas" width="300" height="400"></canvas>
<div id="skipDiv" class="hidden"> <div id="skipDiv" class="hidden">
<button id="skipButton" type="button">Skip Word</button> <button id="skipButton" type="button">Skip Word</button>
</div> </div>
<script src="//cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.1/sockjs.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.1/sockjs.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script src="practice.js"></script> <script src="practice.js"></script>
</body> </body>
</html> </html>

View File

@ -1,187 +1,187 @@
/* Common rules */ /* Common rules */
.centered { .centered {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
} }
.hidden { .hidden {
display: none; display: none;
} }
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
body { body {
font-family: sans-serif; font-family: sans-serif;
background: linen; background: linen;
} }
button { button {
padding: 6px 12px; padding: 6px 12px;
} }
button:hover:enabled { button:hover:enabled {
cursor: pointer; cursor: pointer;
} }
button:hover:disabled { button:hover:disabled {
cursor: not-allowed; cursor: not-allowed;
} }
/* Main area */ /* Main area */
.header { .header {
font-size: 1.6em; font-size: 1.6em;
color: white; color: white;
background: steelblue; background: steelblue;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
padding: 0 0.5em; padding: 0 0.5em;
} }
/* Chat pane */ /* Chat pane */
.message-item { .message-item {
border-top: 1px solid black; border-top: 1px solid black;
padding: 10px; padding: 10px;
position: relative; position: relative;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
white-space: pre-line; white-space: pre-line;
} }
.message-item:first-child { .message-item:first-child {
border-top: none; border-top: none;
} }
.log { .log {
color: green; color: green;
} }
.chat.column textarea { .chat.column textarea {
width: 100%; width: 100%;
height: 100%; height: 100%;
font-size: 14px; font-size: 14px;
background-color: linen; background-color: linen;
border-color: transparent; border-color: transparent;
border-style: none; border-style: none;
outline: none; outline: none;
padding: 0; padding: 0;
resize: none; resize: none;
} }
.chat.column textarea::-webkit-input-placeholder { .chat.column textarea::-webkit-input-placeholder {
color: steelblue; color: steelblue;
} }
.chat.column textarea::-moz-placeholder { .chat.column textarea::-moz-placeholder {
color: steelblue; color: steelblue;
} }
.chat.column textarea:-ms-input-placeholder { .chat.column textarea:-ms-input-placeholder {
color: steelblue; color: steelblue;
} }
/* Lobby pane */ /* Lobby pane */
.panel { .panel {
margin: 20px 0; margin: 20px 0;
border: 1px solid transparent; border: 1px solid transparent;
border-color: #ddd; border-color: #ddd;
border-radius: 4px; border-radius: 4px;
} }
.panel-heading { .panel-heading {
background-color: lightgray; background-color: lightgray;
border-color: #ddd; border-color: #ddd;
padding: 10px 15px; padding: 10px 15px;
} }
.panel-title { .panel-title {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
font-size: 16px; font-size: 16px;
color: steelblue; color: steelblue;
} }
.list-group { .list-group {
margin-bottom: 0; margin-bottom: 0;
} }
.panel>.list-group .list-group-item { .panel>.list-group .list-group-item {
font-size: 1em; font-size: 1em;
border-width: 1px 0; border-width: 1px 0;
border-radius: 0; border-radius: 0;
outline: none; outline: none;
} }
.list-group-item { .list-group-item {
position: relative; position: relative;
display: block; display: block;
padding: 10px 15px; padding: 10px 15px;
margin-bottom: -1px; margin-bottom: -1px;
background-color: white; background-color: white;
border: 1px solid #ddd; border: 1px solid #ddd;
} }
button.list-group-item { button.list-group-item {
width: 100%; width: 100%;
text-align: left; text-align: left;
} }
button.list-group-item:hover { button.list-group-item:hover {
background-color: lightblue; background-color: lightblue;
} }
button.list-group-item:disabled { button.list-group-item:disabled {
color: black; color: black;
} }
button.list-group-item:hover:disabled { button.list-group-item:hover:disabled {
background-color: white; background-color: white;
cursor: default; cursor: default;
} }
.button { .button {
width: 120px; width: 120px;
margin-left: 10px; margin-left: 10px;
border: none; border: none;
outline: none; outline: none;
} }
.create.button { .create.button {
background-color: steelblue; background-color: steelblue;
color: white; color: white;
} }
.leave.button { .leave.button {
background-color: lightcoral; background-color: lightcoral;
color: black; color: black;
} }
/* Nickname pane */ /* Nickname pane */
.form { .form {
padding-top: 48px; padding-top: 48px;
text-align: center; text-align: center;
font-weight: 100; font-weight: 100;
} }
.error-message { .error-message {
color: red; color: red;
font-size: 14px; font-size: 14px;
} }
.form-control { .form-control {
width: 300px; width: 300px;
font-size: 200%; font-size: 200%;
letter-spacing: 3px; letter-spacing: 3px;
background-color: transparent; background-color: transparent;
border: none; border: none;
border-bottom: 2px solid black; border-bottom: 2px solid black;
color: steelblue; color: steelblue;
outline: none; outline: none;
padding-bottom: 15px; padding-bottom: 15px;
text-align: center; text-align: center;
} }