Convert CRLF to LF
This commit is contained in:
parent
c526f1bf3b
commit
a5d9c7eee6
@ -1,22 +1,22 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-client-api</artifactId>
|
||||
<name>Lingo WebSocket :: Client API</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>lingo-websocket-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-client-api</artifactId>
|
||||
<name>Lingo WebSocket :: Client API</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>lingo-websocket-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -1,35 +1,35 @@
|
||||
package lingo.client.api;
|
||||
|
||||
public class Destinations {
|
||||
|
||||
public static final String CHAT = topicDestination("chat");
|
||||
|
||||
public static final String GAME_CLOSED = topicDestination("gameClosed");
|
||||
|
||||
public static final String GAME_HOSTED = topicDestination("gameHosted");
|
||||
|
||||
public static final String GAME_JOINED = topicDestination("gameJoined");
|
||||
|
||||
public static final String GAME_LEFT = topicDestination("gameLeft");
|
||||
|
||||
public static final String OPPONENT_JOINED = topicDestination("opponentJoined");
|
||||
|
||||
public static final String OPPONENT_REPORTS = topicDestination("opponentReports");
|
||||
|
||||
public static final String PLAYER_REPORTS = topicDestination("playerReports");
|
||||
|
||||
public static final String PRACTICE_GAME = topicDestination("practiceGame");
|
||||
|
||||
public static final String PRACTICE_REPORTS = topicDestination("practiceReports");
|
||||
|
||||
public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped");
|
||||
|
||||
public static final String SESSION_USERNAME = topicDestination("sessionUsername");
|
||||
|
||||
public static final String USER_JOINED = topicDestination("userJoined");
|
||||
|
||||
private static String topicDestination(String suffix) {
|
||||
return "/topic/" + suffix;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.api;
|
||||
|
||||
public class Destinations {
|
||||
|
||||
public static final String CHAT = topicDestination("chat");
|
||||
|
||||
public static final String GAME_CLOSED = topicDestination("gameClosed");
|
||||
|
||||
public static final String GAME_HOSTED = topicDestination("gameHosted");
|
||||
|
||||
public static final String GAME_JOINED = topicDestination("gameJoined");
|
||||
|
||||
public static final String GAME_LEFT = topicDestination("gameLeft");
|
||||
|
||||
public static final String OPPONENT_JOINED = topicDestination("opponentJoined");
|
||||
|
||||
public static final String OPPONENT_REPORTS = topicDestination("opponentReports");
|
||||
|
||||
public static final String PLAYER_REPORTS = topicDestination("playerReports");
|
||||
|
||||
public static final String PRACTICE_GAME = topicDestination("practiceGame");
|
||||
|
||||
public static final String PRACTICE_REPORTS = topicDestination("practiceReports");
|
||||
|
||||
public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped");
|
||||
|
||||
public static final String SESSION_USERNAME = topicDestination("sessionUsername");
|
||||
|
||||
public static final String USER_JOINED = topicDestination("userJoined");
|
||||
|
||||
private static String topicDestination(String suffix) {
|
||||
return "/topic/" + suffix;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,43 +1,43 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-client</artifactId>
|
||||
<name>Lingo WebSocket :: Client</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>lingo-websocket-client-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.zenjava</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>8.8.3</version>
|
||||
<configuration>
|
||||
<mainClass>lingo.client.bootstrap.LingoClient</mainClass>
|
||||
<additionalAppResources>src/main/config</additionalAppResources>
|
||||
<copyAdditionalAppResourcesToJar>true</copyAdditionalAppResourcesToJar>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-client</artifactId>
|
||||
<name>Lingo WebSocket :: Client</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>lingo-websocket-client-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.zenjava</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>8.8.3</version>
|
||||
<configuration>
|
||||
<mainClass>lingo.client.bootstrap.LingoClient</mainClass>
|
||||
<additionalAppResources>src/main/config</additionalAppResources>
|
||||
<copyAdditionalAppResourcesToJar>true</copyAdditionalAppResourcesToJar>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -1,9 +1,9 @@
|
||||
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
||||
|
||||
# Development
|
||||
web.base.url: http://localhost:8080
|
||||
web.socket.url: ws://localhost:8080/sockjs
|
||||
|
||||
# Production
|
||||
#web.base.url: http://lingo.charego.com
|
||||
#web.socket.url: ws://lingo.charego.com/sockjs
|
||||
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
||||
|
||||
# Development
|
||||
web.base.url: http://localhost:8080
|
||||
web.socket.url: ws://localhost:8080/sockjs
|
||||
|
||||
# Production
|
||||
#web.base.url: http://lingo.charego.com
|
||||
#web.socket.url: ws://lingo.charego.com/sockjs
|
||||
|
@ -1,65 +1,65 @@
|
||||
package lingo.client.bootstrap;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LingoClient extends Application {
|
||||
|
||||
private Parent root;
|
||||
|
||||
public static void main(final String[] args) {
|
||||
Application.launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
ConfigurableApplicationContext context = startSpringApplication();
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Lingo.fxml"));
|
||||
loader.setControllerFactory(clazz -> context.getBean(clazz));
|
||||
root = loader.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
// Close the Spring context when the client is closed.
|
||||
stage.setOnCloseRequest(e -> {
|
||||
stage.close();
|
||||
System.exit(0);
|
||||
});
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add("/style.css");
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/lingo.png")));
|
||||
stage.setResizable(false);
|
||||
stage.setScene(scene);
|
||||
stage.setTitle("Lingo");
|
||||
stage.show();
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext startSpringApplication() {
|
||||
SpringApplication application = new SpringApplication(LingoClient.class);
|
||||
String[] args = getParameters().getRaw().stream().toArray(String[]::new);
|
||||
application.setHeadless(false);
|
||||
application.setWebEnvironment(false);
|
||||
return application.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecutorService executorService() {
|
||||
return Executors.newFixedThreadPool(5, new CustomizableThreadFactory("ClientThread-"));
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.bootstrap;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LingoClient extends Application {
|
||||
|
||||
private Parent root;
|
||||
|
||||
public static void main(final String[] args) {
|
||||
Application.launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
ConfigurableApplicationContext context = startSpringApplication();
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Lingo.fxml"));
|
||||
loader.setControllerFactory(clazz -> context.getBean(clazz));
|
||||
root = loader.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
// Close the Spring context when the client is closed.
|
||||
stage.setOnCloseRequest(e -> {
|
||||
stage.close();
|
||||
System.exit(0);
|
||||
});
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add("/style.css");
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/lingo.png")));
|
||||
stage.setResizable(false);
|
||||
stage.setScene(scene);
|
||||
stage.setTitle("Lingo");
|
||||
stage.show();
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext startSpringApplication() {
|
||||
SpringApplication application = new SpringApplication(LingoClient.class);
|
||||
String[] args = getParameters().getRaw().stream().toArray(String[]::new);
|
||||
application.setHeadless(false);
|
||||
application.setWebEnvironment(false);
|
||||
return application.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecutorService executorService() {
|
||||
return Executors.newFixedThreadPool(5, new CustomizableThreadFactory("ClientThread-"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,102 +1,102 @@
|
||||
package lingo.client.bootstrap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import lingo.client.multiplayer.MultiplayerConfig;
|
||||
import lingo.client.multiplayer.MultiplayerPresenter;
|
||||
import lingo.client.singleplayer.SinglePlayerPresenter;
|
||||
import lingo.client.util.FxmlController;
|
||||
import lingo.common.WordReader;
|
||||
|
||||
@Component
|
||||
public class LingoPresenter implements FxmlController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LingoPresenter.class);
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext bootstrapContext;
|
||||
|
||||
@Autowired
|
||||
private ExecutorService executorService;
|
||||
|
||||
@FXML
|
||||
private BorderPane content;
|
||||
|
||||
@FXML
|
||||
private BorderPane gameModeChooser;
|
||||
|
||||
@FXML
|
||||
private void exit(ActionEvent event) {
|
||||
Stage stage = (Stage) content.getScene().getWindow();
|
||||
stage.close();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// No initialization needed
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showMultiplayer(ActionEvent event) {
|
||||
log.info("Launching multiplayer...");
|
||||
|
||||
executorService.execute(() -> {
|
||||
AnnotationConfigApplicationContext multiplayerContext = new AnnotationConfigApplicationContext();
|
||||
multiplayerContext.setParent(bootstrapContext);
|
||||
multiplayerContext.scan(MultiplayerConfig.class.getPackage().getName());
|
||||
multiplayerContext.refresh();
|
||||
|
||||
MultiplayerPresenter presenter = multiplayerContext.getBean(MultiplayerPresenter.class);
|
||||
presenter.setOnBackButtonPressed(e -> {
|
||||
multiplayerContext.close();
|
||||
content.setCenter(gameModeChooser);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LingoMultiplayer.fxml"));
|
||||
loader.setControllerFactory(clazz -> multiplayerContext.getBean(clazz));
|
||||
try {
|
||||
content.setCenter(loader.load());
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load multiplayer", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showSinglePlayer(ActionEvent event) {
|
||||
log.info("Launching single player...");
|
||||
|
||||
// TODO: Is there a memory leak here?
|
||||
try {
|
||||
Set<String> guesses = WordReader.readFileToSet("/guesses.txt");
|
||||
List<String> words = WordReader.readFileToList("/words.txt");
|
||||
SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> {
|
||||
content.setCenter(gameModeChooser);
|
||||
});
|
||||
content.setCenter(presenter.getNode());
|
||||
presenter.startGame();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load single player", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.bootstrap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import lingo.client.multiplayer.MultiplayerConfig;
|
||||
import lingo.client.multiplayer.MultiplayerPresenter;
|
||||
import lingo.client.singleplayer.SinglePlayerPresenter;
|
||||
import lingo.client.util.FxmlController;
|
||||
import lingo.common.WordReader;
|
||||
|
||||
@Component
|
||||
public class LingoPresenter implements FxmlController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LingoPresenter.class);
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext bootstrapContext;
|
||||
|
||||
@Autowired
|
||||
private ExecutorService executorService;
|
||||
|
||||
@FXML
|
||||
private BorderPane content;
|
||||
|
||||
@FXML
|
||||
private BorderPane gameModeChooser;
|
||||
|
||||
@FXML
|
||||
private void exit(ActionEvent event) {
|
||||
Stage stage = (Stage) content.getScene().getWindow();
|
||||
stage.close();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// No initialization needed
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showMultiplayer(ActionEvent event) {
|
||||
log.info("Launching multiplayer...");
|
||||
|
||||
executorService.execute(() -> {
|
||||
AnnotationConfigApplicationContext multiplayerContext = new AnnotationConfigApplicationContext();
|
||||
multiplayerContext.setParent(bootstrapContext);
|
||||
multiplayerContext.scan(MultiplayerConfig.class.getPackage().getName());
|
||||
multiplayerContext.refresh();
|
||||
|
||||
MultiplayerPresenter presenter = multiplayerContext.getBean(MultiplayerPresenter.class);
|
||||
presenter.setOnBackButtonPressed(e -> {
|
||||
multiplayerContext.close();
|
||||
content.setCenter(gameModeChooser);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LingoMultiplayer.fxml"));
|
||||
loader.setControllerFactory(clazz -> multiplayerContext.getBean(clazz));
|
||||
try {
|
||||
content.setCenter(loader.load());
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load multiplayer", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showSinglePlayer(ActionEvent event) {
|
||||
log.info("Launching single player...");
|
||||
|
||||
// TODO: Is there a memory leak here?
|
||||
try {
|
||||
Set<String> guesses = WordReader.readFileToSet("/guesses.txt");
|
||||
List<String> words = WordReader.readFileToList("/words.txt");
|
||||
SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> {
|
||||
content.setCenter(gameModeChooser);
|
||||
});
|
||||
content.setCenter(presenter.getNode());
|
||||
presenter.startGame();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load single player", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,60 +1,60 @@
|
||||
package lingo.client.multiplayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.web.client.RootUriTemplateHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.socket.client.WebSocketClient;
|
||||
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||
import org.springframework.web.socket.messaging.WebSocketStompClient;
|
||||
import org.springframework.web.socket.sockjs.client.SockJsClient;
|
||||
import org.springframework.web.socket.sockjs.client.Transport;
|
||||
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
|
||||
|
||||
@Configuration
|
||||
public class MultiplayerConfig {
|
||||
|
||||
@Bean
|
||||
public WebSocketClient webSocketClient() {
|
||||
WebSocketClient webSocketClient = new StandardWebSocketClient();
|
||||
List<Transport> transports = new ArrayList<>();
|
||||
transports.add(new WebSocketTransport(webSocketClient));
|
||||
SockJsClient sockJsClient = new SockJsClient(transports);
|
||||
return sockJsClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketStompClient stompClient(WebSocketClient webSocketClient, MessageConverter messageConverter) {
|
||||
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
|
||||
stompClient.setMessageConverter(messageConverter);
|
||||
stompClient.setTaskScheduler(new ThreadPoolTaskScheduler());
|
||||
return stompClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConverter messageConverter() {
|
||||
List<MessageConverter> converters = new ArrayList<>();
|
||||
converters.add(new StringMessageConverter());
|
||||
converters.add(new ByteArrayMessageConverter());
|
||||
converters.add(new MappingJackson2MessageConverter());
|
||||
return new CompositeMessageConverter(converters);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(Environment env) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url")));
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.multiplayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.web.client.RootUriTemplateHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.socket.client.WebSocketClient;
|
||||
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||
import org.springframework.web.socket.messaging.WebSocketStompClient;
|
||||
import org.springframework.web.socket.sockjs.client.SockJsClient;
|
||||
import org.springframework.web.socket.sockjs.client.Transport;
|
||||
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
|
||||
|
||||
@Configuration
|
||||
public class MultiplayerConfig {
|
||||
|
||||
@Bean
|
||||
public WebSocketClient webSocketClient() {
|
||||
WebSocketClient webSocketClient = new StandardWebSocketClient();
|
||||
List<Transport> transports = new ArrayList<>();
|
||||
transports.add(new WebSocketTransport(webSocketClient));
|
||||
SockJsClient sockJsClient = new SockJsClient(transports);
|
||||
return sockJsClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketStompClient stompClient(WebSocketClient webSocketClient, MessageConverter messageConverter) {
|
||||
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
|
||||
stompClient.setMessageConverter(messageConverter);
|
||||
stompClient.setTaskScheduler(new ThreadPoolTaskScheduler());
|
||||
return stompClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConverter messageConverter() {
|
||||
List<MessageConverter> converters = new ArrayList<>();
|
||||
converters.add(new StringMessageConverter());
|
||||
converters.add(new ByteArrayMessageConverter());
|
||||
converters.add(new MappingJackson2MessageConverter());
|
||||
return new CompositeMessageConverter(converters);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(Environment env) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url")));
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,426 +1,426 @@
|
||||
package lingo.client.multiplayer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.messaging.simp.stomp.StompFrameHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.scene.web.WebView;
|
||||
import lingo.client.api.Destinations;
|
||||
import lingo.client.util.FxmlController;
|
||||
import lingo.client.view.Board;
|
||||
import lingo.client.view.OpponentBoard;
|
||||
import lingo.client.view.PlayerBoard;
|
||||
import lingo.common.Game;
|
||||
import lingo.common.GameLeftMessage;
|
||||
import lingo.common.Report;
|
||||
|
||||
@Component
|
||||
public class MultiplayerPresenter implements FxmlController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MultiplayerPresenter.class);
|
||||
|
||||
private static final double MARGIN_BOTTOM = 75;
|
||||
|
||||
@FXML
|
||||
private Button backButton;
|
||||
|
||||
@FXML
|
||||
private Canvas canvas;
|
||||
|
||||
@FXML
|
||||
private StackPane contentPane;
|
||||
|
||||
@FXML
|
||||
private WebView webView;
|
||||
|
||||
@Autowired
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private StompTemplate stompTemplate;
|
||||
|
||||
private EventHandler<ActionEvent> backButtonHandler;
|
||||
|
||||
private GraphicsContext gc;
|
||||
|
||||
private String lastWord;
|
||||
|
||||
private PlayerBoard playerBoard;
|
||||
|
||||
private OpponentBoard opponentBoard;
|
||||
|
||||
private final CountDownLatch subscriptionsLatch = new CountDownLatch(7);
|
||||
|
||||
private String username;
|
||||
|
||||
private String opponentUsername;
|
||||
|
||||
private void clearBoards(boolean clearScore) {
|
||||
playerBoard.clearBoard();
|
||||
opponentBoard.clearBoard();
|
||||
if (clearScore) {
|
||||
playerBoard.clearScore();
|
||||
opponentBoard.clearScore();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawLastWord() {
|
||||
if (lastWord != null) {
|
||||
double x = canvas.getWidth() / 2;
|
||||
double y = canvas.getHeight() - MARGIN_BOTTOM / 2;
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillText("Previous word: " + lastWord.toUpperCase(), x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// Needed for key event handling
|
||||
canvas.setFocusTraversable(true);
|
||||
|
||||
gc = canvas.getGraphicsContext2D();
|
||||
gc.setFont(Font.font(24));
|
||||
gc.setTextAlign(TextAlignment.CENTER);
|
||||
gc.setTextBaseline(VPos.CENTER);
|
||||
playerBoard = new PlayerBoard(canvas, 50, 50);
|
||||
opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
String html = getClass().getResource("/cube-grid.html").toExternalForm();
|
||||
String css = getClass().getResource("/cube-grid.css").toExternalForm();
|
||||
webView.getEngine().load(html);
|
||||
webView.getEngine().setUserStyleSheetLocation(css);
|
||||
webView.setContextMenuEnabled(false);
|
||||
repaint();
|
||||
});
|
||||
|
||||
backButton.setOnAction(backButtonHandler);
|
||||
|
||||
executorService.execute(() -> {
|
||||
while (subscriptionsLatch.getCount() != 0) {
|
||||
try {
|
||||
subscriptionsLatch.await();
|
||||
} catch (InterruptedException ok) {
|
||||
ok.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
username = UUID.randomUUID().toString().substring(0, 8);
|
||||
stompTemplate.getSession().send("/app/setUsername", username);
|
||||
|
||||
Collection<Game> games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody();
|
||||
boolean joinedGame = false;
|
||||
for (Game game : games) {
|
||||
if (game.getPlayerTwo() == null) {
|
||||
stompTemplate.getSession().send("/app/joinGame", game.getId());
|
||||
joinedGame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!joinedGame) {
|
||||
stompTemplate.getSession().send("/app/hostGame", null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void keyPressed(KeyEvent e) {
|
||||
final KeyCode keyCode = e.getCode();
|
||||
if (keyCode == KeyCode.BACK_SPACE) {
|
||||
if (playerBoard.handleBackspace()) {
|
||||
repaint();
|
||||
}
|
||||
} else if (keyCode == KeyCode.ENTER) {
|
||||
final String guess = playerBoard.handleEnter();
|
||||
if (guess != null) {
|
||||
executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess));
|
||||
repaint();
|
||||
}
|
||||
} else if (keyCode.isLetterKey()) {
|
||||
if (playerBoard.handleLetter(keyCode.getName())) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void newWord(String firstLetter) {
|
||||
playerBoard.setProgress(0, firstLetter.charAt(0));
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
executorService.execute(() -> {
|
||||
stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
});
|
||||
}
|
||||
|
||||
private void repaint() {
|
||||
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
playerBoard.drawBoard();
|
||||
opponentBoard.drawBoard();
|
||||
drawLastWord();
|
||||
}
|
||||
|
||||
public void setOnBackButtonPressed(EventHandler<ActionEvent> handler) {
|
||||
backButtonHandler = handler;
|
||||
}
|
||||
|
||||
private void showWaitingAnimation(boolean show) {
|
||||
if (show) {
|
||||
contentPane.getChildren().add(webView);
|
||||
backButton.toFront();
|
||||
} else {
|
||||
contentPane.getChildren().remove(webView);
|
||||
}
|
||||
}
|
||||
|
||||
private class GameClosedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Game.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Game) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Game game) {
|
||||
log.debug("{} closed Game {}", game.getPlayerOne(), game.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private class GameHostedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Game.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Game) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Game game) {
|
||||
log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private class GameJoinedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Game.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Game) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Game game) {
|
||||
log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne());
|
||||
}
|
||||
}
|
||||
|
||||
private class GameLeftHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return GameLeftMessage.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((GameLeftMessage) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(GameLeftMessage message) {
|
||||
final Game game = message.getGame();
|
||||
final String gameLeaver = message.getGameLeaver().getUsername();
|
||||
log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne());
|
||||
if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) {
|
||||
Platform.runLater(() -> {
|
||||
clearBoards(true);
|
||||
showWaitingAnimation(true);
|
||||
opponentUsername = null;
|
||||
lastWord = null;
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OpponentJoinedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return String[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((String[]) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(String[] message) {
|
||||
final String firstLetter = message[0];
|
||||
opponentUsername = message[1];
|
||||
Platform.runLater(() -> {
|
||||
clearBoards(true);
|
||||
newWord(firstLetter);
|
||||
showWaitingAnimation(false);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class OpponentReportHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Report.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Report) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Report report) {
|
||||
if (report.isCorrect()) {
|
||||
onCorrectGuess(report);
|
||||
} else {
|
||||
onIncorrectGuess(report);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final String firstLetter = report.getFirstLetter();
|
||||
log.info("Opponent guessed correctly: " + guess);
|
||||
Platform.runLater(() -> {
|
||||
opponentBoard.addToScore(100);
|
||||
lastWord = guess;
|
||||
clearBoards(false);
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onIncorrectGuess(Report report) {
|
||||
final int[] result = report.getResult();
|
||||
log.info("Opponent result: " + Arrays.toString(result));
|
||||
Platform.runLater(() -> {
|
||||
opponentBoard.addResult(result);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerReportHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Report.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Report) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Report report) {
|
||||
if (report.isCorrect()) {
|
||||
onCorrectGuess(report);
|
||||
} else {
|
||||
onIncorrectGuess(report);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final String firstLetter = report.getFirstLetter();
|
||||
log.info("I guessed correctly!");
|
||||
Platform.runLater(() -> {
|
||||
playerBoard.addToScore(100);
|
||||
lastWord = guess;
|
||||
clearBoards(false);
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onIncorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final int[] result = report.getResult();
|
||||
log.info("My result: " + Arrays.toString(result));
|
||||
Platform.runLater(() -> {
|
||||
if (Game.isInvalid(result)) {
|
||||
playerBoard.addGuess("-----");
|
||||
} else {
|
||||
for (int i = 0; i < Game.WORD_LENGTH; i++) {
|
||||
if (result[i] == Game.CORRECT_CHARACTER) {
|
||||
playerBoard.setProgress(i, guess.charAt(i));
|
||||
}
|
||||
}
|
||||
playerBoard.addGuess(guess);
|
||||
}
|
||||
playerBoard.addResult(result);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class GameList extends ParameterizedTypeReference<Collection<Game>> {
|
||||
// intentionally left empty
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.multiplayer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.messaging.simp.stomp.StompFrameHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.scene.web.WebView;
|
||||
import lingo.client.api.Destinations;
|
||||
import lingo.client.util.FxmlController;
|
||||
import lingo.client.view.Board;
|
||||
import lingo.client.view.OpponentBoard;
|
||||
import lingo.client.view.PlayerBoard;
|
||||
import lingo.common.Game;
|
||||
import lingo.common.GameLeftMessage;
|
||||
import lingo.common.Report;
|
||||
|
||||
@Component
|
||||
public class MultiplayerPresenter implements FxmlController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MultiplayerPresenter.class);
|
||||
|
||||
private static final double MARGIN_BOTTOM = 75;
|
||||
|
||||
@FXML
|
||||
private Button backButton;
|
||||
|
||||
@FXML
|
||||
private Canvas canvas;
|
||||
|
||||
@FXML
|
||||
private StackPane contentPane;
|
||||
|
||||
@FXML
|
||||
private WebView webView;
|
||||
|
||||
@Autowired
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private StompTemplate stompTemplate;
|
||||
|
||||
private EventHandler<ActionEvent> backButtonHandler;
|
||||
|
||||
private GraphicsContext gc;
|
||||
|
||||
private String lastWord;
|
||||
|
||||
private PlayerBoard playerBoard;
|
||||
|
||||
private OpponentBoard opponentBoard;
|
||||
|
||||
private final CountDownLatch subscriptionsLatch = new CountDownLatch(7);
|
||||
|
||||
private String username;
|
||||
|
||||
private String opponentUsername;
|
||||
|
||||
private void clearBoards(boolean clearScore) {
|
||||
playerBoard.clearBoard();
|
||||
opponentBoard.clearBoard();
|
||||
if (clearScore) {
|
||||
playerBoard.clearScore();
|
||||
opponentBoard.clearScore();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawLastWord() {
|
||||
if (lastWord != null) {
|
||||
double x = canvas.getWidth() / 2;
|
||||
double y = canvas.getHeight() - MARGIN_BOTTOM / 2;
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillText("Previous word: " + lastWord.toUpperCase(), x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// Needed for key event handling
|
||||
canvas.setFocusTraversable(true);
|
||||
|
||||
gc = canvas.getGraphicsContext2D();
|
||||
gc.setFont(Font.font(24));
|
||||
gc.setTextAlign(TextAlignment.CENTER);
|
||||
gc.setTextBaseline(VPos.CENTER);
|
||||
playerBoard = new PlayerBoard(canvas, 50, 50);
|
||||
opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
String html = getClass().getResource("/cube-grid.html").toExternalForm();
|
||||
String css = getClass().getResource("/cube-grid.css").toExternalForm();
|
||||
webView.getEngine().load(html);
|
||||
webView.getEngine().setUserStyleSheetLocation(css);
|
||||
webView.setContextMenuEnabled(false);
|
||||
repaint();
|
||||
});
|
||||
|
||||
backButton.setOnAction(backButtonHandler);
|
||||
|
||||
executorService.execute(() -> {
|
||||
while (subscriptionsLatch.getCount() != 0) {
|
||||
try {
|
||||
subscriptionsLatch.await();
|
||||
} catch (InterruptedException ok) {
|
||||
ok.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
username = UUID.randomUUID().toString().substring(0, 8);
|
||||
stompTemplate.getSession().send("/app/setUsername", username);
|
||||
|
||||
Collection<Game> games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody();
|
||||
boolean joinedGame = false;
|
||||
for (Game game : games) {
|
||||
if (game.getPlayerTwo() == null) {
|
||||
stompTemplate.getSession().send("/app/joinGame", game.getId());
|
||||
joinedGame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!joinedGame) {
|
||||
stompTemplate.getSession().send("/app/hostGame", null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void keyPressed(KeyEvent e) {
|
||||
final KeyCode keyCode = e.getCode();
|
||||
if (keyCode == KeyCode.BACK_SPACE) {
|
||||
if (playerBoard.handleBackspace()) {
|
||||
repaint();
|
||||
}
|
||||
} else if (keyCode == KeyCode.ENTER) {
|
||||
final String guess = playerBoard.handleEnter();
|
||||
if (guess != null) {
|
||||
executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess));
|
||||
repaint();
|
||||
}
|
||||
} else if (keyCode.isLetterKey()) {
|
||||
if (playerBoard.handleLetter(keyCode.getName())) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void newWord(String firstLetter) {
|
||||
playerBoard.setProgress(0, firstLetter.charAt(0));
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
executorService.execute(() -> {
|
||||
stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(),
|
||||
subscription -> subscriptionsLatch.countDown());
|
||||
});
|
||||
}
|
||||
|
||||
private void repaint() {
|
||||
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
playerBoard.drawBoard();
|
||||
opponentBoard.drawBoard();
|
||||
drawLastWord();
|
||||
}
|
||||
|
||||
public void setOnBackButtonPressed(EventHandler<ActionEvent> handler) {
|
||||
backButtonHandler = handler;
|
||||
}
|
||||
|
||||
private void showWaitingAnimation(boolean show) {
|
||||
if (show) {
|
||||
contentPane.getChildren().add(webView);
|
||||
backButton.toFront();
|
||||
} else {
|
||||
contentPane.getChildren().remove(webView);
|
||||
}
|
||||
}
|
||||
|
||||
private class GameClosedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Game.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Game) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Game game) {
|
||||
log.debug("{} closed Game {}", game.getPlayerOne(), game.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private class GameHostedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Game.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Game) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Game game) {
|
||||
log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private class GameJoinedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Game.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Game) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Game game) {
|
||||
log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne());
|
||||
}
|
||||
}
|
||||
|
||||
private class GameLeftHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return GameLeftMessage.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((GameLeftMessage) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(GameLeftMessage message) {
|
||||
final Game game = message.getGame();
|
||||
final String gameLeaver = message.getGameLeaver().getUsername();
|
||||
log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne());
|
||||
if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) {
|
||||
Platform.runLater(() -> {
|
||||
clearBoards(true);
|
||||
showWaitingAnimation(true);
|
||||
opponentUsername = null;
|
||||
lastWord = null;
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OpponentJoinedHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return String[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((String[]) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(String[] message) {
|
||||
final String firstLetter = message[0];
|
||||
opponentUsername = message[1];
|
||||
Platform.runLater(() -> {
|
||||
clearBoards(true);
|
||||
newWord(firstLetter);
|
||||
showWaitingAnimation(false);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class OpponentReportHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Report.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Report) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Report report) {
|
||||
if (report.isCorrect()) {
|
||||
onCorrectGuess(report);
|
||||
} else {
|
||||
onIncorrectGuess(report);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final String firstLetter = report.getFirstLetter();
|
||||
log.info("Opponent guessed correctly: " + guess);
|
||||
Platform.runLater(() -> {
|
||||
opponentBoard.addToScore(100);
|
||||
lastWord = guess;
|
||||
clearBoards(false);
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onIncorrectGuess(Report report) {
|
||||
final int[] result = report.getResult();
|
||||
log.info("Opponent result: " + Arrays.toString(result));
|
||||
Platform.runLater(() -> {
|
||||
opponentBoard.addResult(result);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerReportHandler implements StompFrameHandler {
|
||||
|
||||
@Override
|
||||
public Type getPayloadType(StompHeaders headers) {
|
||||
return Report.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFrame(StompHeaders headers, Object payload) {
|
||||
handleMessage((Report) payload);
|
||||
}
|
||||
|
||||
private void handleMessage(Report report) {
|
||||
if (report.isCorrect()) {
|
||||
onCorrectGuess(report);
|
||||
} else {
|
||||
onIncorrectGuess(report);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final String firstLetter = report.getFirstLetter();
|
||||
log.info("I guessed correctly!");
|
||||
Platform.runLater(() -> {
|
||||
playerBoard.addToScore(100);
|
||||
lastWord = guess;
|
||||
clearBoards(false);
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onIncorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final int[] result = report.getResult();
|
||||
log.info("My result: " + Arrays.toString(result));
|
||||
Platform.runLater(() -> {
|
||||
if (Game.isInvalid(result)) {
|
||||
playerBoard.addGuess("-----");
|
||||
} else {
|
||||
for (int i = 0; i < Game.WORD_LENGTH; i++) {
|
||||
if (result[i] == Game.CORRECT_CHARACTER) {
|
||||
playerBoard.setProgress(i, guess.charAt(i));
|
||||
}
|
||||
}
|
||||
playerBoard.addGuess(guess);
|
||||
}
|
||||
playerBoard.addResult(result);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class GameList extends ParameterizedTypeReference<Collection<Game>> {
|
||||
// intentionally left empty
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,126 +1,126 @@
|
||||
package lingo.client.multiplayer;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.messaging.simp.stomp.StompFrameHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||
import org.springframework.messaging.simp.stomp.StompSession;
|
||||
import org.springframework.messaging.simp.stomp.StompSession.Subscription;
|
||||
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.messaging.WebSocketStompClient;
|
||||
|
||||
@Component
|
||||
public class StompTemplate {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(StompTemplate.class);
|
||||
|
||||
@Value("${web.socket.url}")
|
||||
private String webSocketUrl;
|
||||
|
||||
@Autowired
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Autowired
|
||||
private WebSocketStompClient stompClient;
|
||||
|
||||
private StompSession stompSession;
|
||||
|
||||
private final BlockingQueue<SubscriptionRequest> subscriptionRequests = new LinkedBlockingQueue<>();
|
||||
|
||||
public StompSession getSession() {
|
||||
/*
|
||||
* TODO: If STOMP session is null or disconnected, create a new
|
||||
* connection before returning this field.
|
||||
*/
|
||||
return stompSession;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
executorService.execute(() -> stompClient.connect(webSocketUrl, new WebSocketSessionHandler()));
|
||||
new Thread(new WebSocketSessionListener()).start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void preDestroy() {
|
||||
if (stompSession != null) {
|
||||
log.info("Disconnecting from STOMP endpoint...");
|
||||
stompSession.disconnect();
|
||||
}
|
||||
stompClient.stop();
|
||||
}
|
||||
|
||||
public void subscribe(String destination, StompFrameHandler handler) {
|
||||
subscribe(destination, handler, null);
|
||||
}
|
||||
|
||||
public void subscribe(String destination, StompFrameHandler handler, Consumer<Subscription> callback) {
|
||||
try {
|
||||
subscriptionRequests.put(new SubscriptionRequest(destination, handler, callback));
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Failed to subscribe to destination: {}", destination, e);
|
||||
}
|
||||
}
|
||||
|
||||
private class SubscriptionRequest {
|
||||
public final String destination;
|
||||
public final StompFrameHandler handler;
|
||||
public final Consumer<Subscription> callback;
|
||||
|
||||
public SubscriptionRequest(String destination, StompFrameHandler handler, Consumer<Subscription> callback) {
|
||||
this.destination = destination;
|
||||
this.handler = handler;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void onSubscribed(Subscription subscription) {
|
||||
if (callback != null) {
|
||||
callback.accept(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WebSocketSessionHandler extends StompSessionHandlerAdapter {
|
||||
@Override
|
||||
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
|
||||
log.info("Connected to STOMP endpoint");
|
||||
stompSession = session;
|
||||
}
|
||||
}
|
||||
|
||||
private class WebSocketSessionListener implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
if (stompSession == null) {
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException ok) {
|
||||
ok.printStackTrace();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
final SubscriptionRequest request = subscriptionRequests.take();
|
||||
final Subscription subscription = stompSession.subscribe(request.destination, request.handler);
|
||||
request.onSubscribed(subscription);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Failed to subscribe", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.multiplayer;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.messaging.simp.stomp.StompFrameHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||
import org.springframework.messaging.simp.stomp.StompSession;
|
||||
import org.springframework.messaging.simp.stomp.StompSession.Subscription;
|
||||
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.messaging.WebSocketStompClient;
|
||||
|
||||
@Component
|
||||
public class StompTemplate {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(StompTemplate.class);
|
||||
|
||||
@Value("${web.socket.url}")
|
||||
private String webSocketUrl;
|
||||
|
||||
@Autowired
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Autowired
|
||||
private WebSocketStompClient stompClient;
|
||||
|
||||
private StompSession stompSession;
|
||||
|
||||
private final BlockingQueue<SubscriptionRequest> subscriptionRequests = new LinkedBlockingQueue<>();
|
||||
|
||||
public StompSession getSession() {
|
||||
/*
|
||||
* TODO: If STOMP session is null or disconnected, create a new
|
||||
* connection before returning this field.
|
||||
*/
|
||||
return stompSession;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
executorService.execute(() -> stompClient.connect(webSocketUrl, new WebSocketSessionHandler()));
|
||||
new Thread(new WebSocketSessionListener()).start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void preDestroy() {
|
||||
if (stompSession != null) {
|
||||
log.info("Disconnecting from STOMP endpoint...");
|
||||
stompSession.disconnect();
|
||||
}
|
||||
stompClient.stop();
|
||||
}
|
||||
|
||||
public void subscribe(String destination, StompFrameHandler handler) {
|
||||
subscribe(destination, handler, null);
|
||||
}
|
||||
|
||||
public void subscribe(String destination, StompFrameHandler handler, Consumer<Subscription> callback) {
|
||||
try {
|
||||
subscriptionRequests.put(new SubscriptionRequest(destination, handler, callback));
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Failed to subscribe to destination: {}", destination, e);
|
||||
}
|
||||
}
|
||||
|
||||
private class SubscriptionRequest {
|
||||
public final String destination;
|
||||
public final StompFrameHandler handler;
|
||||
public final Consumer<Subscription> callback;
|
||||
|
||||
public SubscriptionRequest(String destination, StompFrameHandler handler, Consumer<Subscription> callback) {
|
||||
this.destination = destination;
|
||||
this.handler = handler;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void onSubscribed(Subscription subscription) {
|
||||
if (callback != null) {
|
||||
callback.accept(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WebSocketSessionHandler extends StompSessionHandlerAdapter {
|
||||
@Override
|
||||
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
|
||||
log.info("Connected to STOMP endpoint");
|
||||
stompSession = session;
|
||||
}
|
||||
}
|
||||
|
||||
private class WebSocketSessionListener implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
if (stompSession == null) {
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException ok) {
|
||||
ok.printStackTrace();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
final SubscriptionRequest request = subscriptionRequests.take();
|
||||
final Subscription subscription = stompSession.subscribe(request.destination, request.handler);
|
||||
request.onSubscribed(subscription);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Failed to subscribe", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,161 +1,161 @@
|
||||
package lingo.client.singleplayer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import lingo.client.view.PlayerBoard;
|
||||
import lingo.common.Game;
|
||||
import lingo.common.Player;
|
||||
import lingo.common.Report;
|
||||
|
||||
public class SinglePlayerPresenter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SinglePlayerPresenter.class);
|
||||
|
||||
private final Button backButton;
|
||||
|
||||
private final Canvas canvas;
|
||||
|
||||
private final StackPane contentPane;
|
||||
|
||||
private final GraphicsContext gc;
|
||||
|
||||
private final PlayerBoard gameBoard;
|
||||
|
||||
private final Game game;
|
||||
|
||||
public SinglePlayerPresenter(List<String> words, Set<String> guesses, EventHandler<ActionEvent> backButtonHandler) {
|
||||
backButton = new Button("Back");
|
||||
backButton.getStyleClass().add("game-nav");
|
||||
StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT);
|
||||
StackPane.setMargin(backButton, new Insets(0, 0, 10, 10));
|
||||
backButton.setOnAction(backButtonHandler);
|
||||
backButton.setPrefWidth(50);
|
||||
|
||||
canvas = new Canvas(650, 420);
|
||||
canvas.setFocusTraversable(true);
|
||||
canvas.setOnKeyPressed(e -> keyPressed(e));
|
||||
gc = canvas.getGraphicsContext2D();
|
||||
gc.setFont(Font.font(24));
|
||||
gc.setTextAlign(TextAlignment.CENTER);
|
||||
gc.setTextBaseline(VPos.CENTER);
|
||||
|
||||
contentPane = new StackPane();
|
||||
contentPane.getChildren().add(canvas);
|
||||
contentPane.getChildren().add(backButton);
|
||||
|
||||
gameBoard = new PlayerBoard(canvas, 200, 60);
|
||||
game = new Game(new Player("solo"));
|
||||
game.setAcceptableGuesses(guesses);
|
||||
game.setPossibleWords(words);
|
||||
}
|
||||
|
||||
private void clearBoards(boolean clearScore) {
|
||||
gameBoard.clearBoard();
|
||||
if (clearScore) {
|
||||
gameBoard.clearScore();
|
||||
}
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
private void keyPressed(KeyEvent e) {
|
||||
final KeyCode keyCode = e.getCode();
|
||||
if (keyCode == KeyCode.BACK_SPACE) {
|
||||
if (gameBoard.handleBackspace()) {
|
||||
repaint();
|
||||
}
|
||||
} else if (keyCode == KeyCode.ENTER) {
|
||||
final String guess = gameBoard.handleEnter();
|
||||
if (guess != null) {
|
||||
final int[] result = game.evaluate(guess);
|
||||
Report report = new Report();
|
||||
report.setGuess(guess);
|
||||
report.setResult(result);
|
||||
if (Game.isCorrect(result)) {
|
||||
final String newWord = game.newWord();
|
||||
final String firstLetter = String.valueOf(newWord.charAt(0));
|
||||
report.setCorrect(true);
|
||||
report.setFirstLetter(firstLetter);
|
||||
report.setResult(result);
|
||||
onCorrectGuess(report);
|
||||
} else {
|
||||
onIncorrectGuess(report);
|
||||
}
|
||||
}
|
||||
} else if (keyCode.isLetterKey()) {
|
||||
if (gameBoard.handleLetter(keyCode.getName())) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void newWord(String firstLetter) {
|
||||
gameBoard.setProgress(0, firstLetter.charAt(0));
|
||||
}
|
||||
|
||||
private void onCorrectGuess(Report report) {
|
||||
final String firstLetter = report.getFirstLetter();
|
||||
log.info("I guessed correctly!");
|
||||
Platform.runLater(() -> {
|
||||
gameBoard.addToScore(100);
|
||||
clearBoards(false);
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onIncorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final int[] result = report.getResult();
|
||||
log.info("My result: " + Arrays.toString(result));
|
||||
Platform.runLater(() -> {
|
||||
if (Game.isInvalid(result)) {
|
||||
gameBoard.addGuess("-----");
|
||||
} else {
|
||||
for (int i = 0; i < Game.WORD_LENGTH; i++) {
|
||||
if (result[i] == Game.CORRECT_CHARACTER) {
|
||||
gameBoard.setProgress(i, guess.charAt(i));
|
||||
}
|
||||
}
|
||||
gameBoard.addGuess(guess);
|
||||
}
|
||||
gameBoard.addResult(result);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void repaint() {
|
||||
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
gameBoard.drawBoard();
|
||||
}
|
||||
|
||||
public void startGame() {
|
||||
final String firstWord = game.newGame();
|
||||
final String firstLetter = String.valueOf(firstWord.charAt(0));
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.singleplayer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import lingo.client.view.PlayerBoard;
|
||||
import lingo.common.Game;
|
||||
import lingo.common.Player;
|
||||
import lingo.common.Report;
|
||||
|
||||
public class SinglePlayerPresenter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SinglePlayerPresenter.class);
|
||||
|
||||
private final Button backButton;
|
||||
|
||||
private final Canvas canvas;
|
||||
|
||||
private final StackPane contentPane;
|
||||
|
||||
private final GraphicsContext gc;
|
||||
|
||||
private final PlayerBoard gameBoard;
|
||||
|
||||
private final Game game;
|
||||
|
||||
public SinglePlayerPresenter(List<String> words, Set<String> guesses, EventHandler<ActionEvent> backButtonHandler) {
|
||||
backButton = new Button("Back");
|
||||
backButton.getStyleClass().add("game-nav");
|
||||
StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT);
|
||||
StackPane.setMargin(backButton, new Insets(0, 0, 10, 10));
|
||||
backButton.setOnAction(backButtonHandler);
|
||||
backButton.setPrefWidth(50);
|
||||
|
||||
canvas = new Canvas(650, 420);
|
||||
canvas.setFocusTraversable(true);
|
||||
canvas.setOnKeyPressed(e -> keyPressed(e));
|
||||
gc = canvas.getGraphicsContext2D();
|
||||
gc.setFont(Font.font(24));
|
||||
gc.setTextAlign(TextAlignment.CENTER);
|
||||
gc.setTextBaseline(VPos.CENTER);
|
||||
|
||||
contentPane = new StackPane();
|
||||
contentPane.getChildren().add(canvas);
|
||||
contentPane.getChildren().add(backButton);
|
||||
|
||||
gameBoard = new PlayerBoard(canvas, 200, 60);
|
||||
game = new Game(new Player("solo"));
|
||||
game.setAcceptableGuesses(guesses);
|
||||
game.setPossibleWords(words);
|
||||
}
|
||||
|
||||
private void clearBoards(boolean clearScore) {
|
||||
gameBoard.clearBoard();
|
||||
if (clearScore) {
|
||||
gameBoard.clearScore();
|
||||
}
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
private void keyPressed(KeyEvent e) {
|
||||
final KeyCode keyCode = e.getCode();
|
||||
if (keyCode == KeyCode.BACK_SPACE) {
|
||||
if (gameBoard.handleBackspace()) {
|
||||
repaint();
|
||||
}
|
||||
} else if (keyCode == KeyCode.ENTER) {
|
||||
final String guess = gameBoard.handleEnter();
|
||||
if (guess != null) {
|
||||
final int[] result = game.evaluate(guess);
|
||||
Report report = new Report();
|
||||
report.setGuess(guess);
|
||||
report.setResult(result);
|
||||
if (Game.isCorrect(result)) {
|
||||
final String newWord = game.newWord();
|
||||
final String firstLetter = String.valueOf(newWord.charAt(0));
|
||||
report.setCorrect(true);
|
||||
report.setFirstLetter(firstLetter);
|
||||
report.setResult(result);
|
||||
onCorrectGuess(report);
|
||||
} else {
|
||||
onIncorrectGuess(report);
|
||||
}
|
||||
}
|
||||
} else if (keyCode.isLetterKey()) {
|
||||
if (gameBoard.handleLetter(keyCode.getName())) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void newWord(String firstLetter) {
|
||||
gameBoard.setProgress(0, firstLetter.charAt(0));
|
||||
}
|
||||
|
||||
private void onCorrectGuess(Report report) {
|
||||
final String firstLetter = report.getFirstLetter();
|
||||
log.info("I guessed correctly!");
|
||||
Platform.runLater(() -> {
|
||||
gameBoard.addToScore(100);
|
||||
clearBoards(false);
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onIncorrectGuess(Report report) {
|
||||
final String guess = report.getGuess();
|
||||
final int[] result = report.getResult();
|
||||
log.info("My result: " + Arrays.toString(result));
|
||||
Platform.runLater(() -> {
|
||||
if (Game.isInvalid(result)) {
|
||||
gameBoard.addGuess("-----");
|
||||
} else {
|
||||
for (int i = 0; i < Game.WORD_LENGTH; i++) {
|
||||
if (result[i] == Game.CORRECT_CHARACTER) {
|
||||
gameBoard.setProgress(i, guess.charAt(i));
|
||||
}
|
||||
}
|
||||
gameBoard.addGuess(guess);
|
||||
}
|
||||
gameBoard.addResult(result);
|
||||
repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void repaint() {
|
||||
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
gameBoard.drawBoard();
|
||||
}
|
||||
|
||||
public void startGame() {
|
||||
final String firstWord = game.newGame();
|
||||
final String firstLetter = String.valueOf(firstWord.charAt(0));
|
||||
newWord(firstLetter);
|
||||
repaint();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
package lingo.client.util;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
|
||||
/**
|
||||
* Identifies a controller that will be created by an {@link FXMLLoader}. The
|
||||
* {@code FXMLLoader} will automatically inject {@code location} and
|
||||
* {@code resources} properties into the controller, and then it will call the
|
||||
* no-arg {@link #initialize()} method. This is the recommended approach: don't
|
||||
* use the {@link Initializable} interface.
|
||||
*/
|
||||
public interface FxmlController {
|
||||
|
||||
/**
|
||||
* Called by the {@link FXMLLoader} to initialize a controller after its
|
||||
* root element has been completely processed. This means all of the
|
||||
* controller's {@link FXML} elements will be injected, and they can be used
|
||||
* to wire up the GUI in ways that couldn't be accomplished using pure FXML,
|
||||
* e.g. attaching property listeners.
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
}
|
||||
package lingo.client.util;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
|
||||
/**
|
||||
* Identifies a controller that will be created by an {@link FXMLLoader}. The
|
||||
* {@code FXMLLoader} will automatically inject {@code location} and
|
||||
* {@code resources} properties into the controller, and then it will call the
|
||||
* no-arg {@link #initialize()} method. This is the recommended approach: don't
|
||||
* use the {@link Initializable} interface.
|
||||
*/
|
||||
public interface FxmlController {
|
||||
|
||||
/**
|
||||
* Called by the {@link FXMLLoader} to initialize a controller after its
|
||||
* root element has been completely processed. This means all of the
|
||||
* controller's {@link FXML} elements will be injected, and they can be used
|
||||
* to wire up the GUI in ways that couldn't be accomplished using pure FXML,
|
||||
* e.g. attaching property listeners.
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
}
|
||||
|
@ -1,35 +1,35 @@
|
||||
package lingo.client.view;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
|
||||
public abstract class Board {
|
||||
|
||||
public static final double HEIGHT = 300;
|
||||
public static final double WIDTH = 250;
|
||||
public static final double SIDE = 50;
|
||||
|
||||
protected final Canvas canvas;
|
||||
|
||||
protected final GraphicsContext gc;
|
||||
|
||||
/** The leftmost x-coordinate */
|
||||
protected final double xInit;
|
||||
|
||||
/** The topmost y-coordinate */
|
||||
protected final double yInit;
|
||||
|
||||
public Board(Canvas canvas, double xInit, double yInit) {
|
||||
this.canvas = canvas;
|
||||
this.gc = canvas.getGraphicsContext2D();
|
||||
this.xInit = xInit;
|
||||
this.yInit = yInit;
|
||||
}
|
||||
|
||||
public void clearBoard() {
|
||||
gc.clearRect(xInit, yInit, WIDTH, HEIGHT);
|
||||
}
|
||||
|
||||
public abstract void drawBoard();
|
||||
|
||||
}
|
||||
package lingo.client.view;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
|
||||
public abstract class Board {
|
||||
|
||||
public static final double HEIGHT = 300;
|
||||
public static final double WIDTH = 250;
|
||||
public static final double SIDE = 50;
|
||||
|
||||
protected final Canvas canvas;
|
||||
|
||||
protected final GraphicsContext gc;
|
||||
|
||||
/** The leftmost x-coordinate */
|
||||
protected final double xInit;
|
||||
|
||||
/** The topmost y-coordinate */
|
||||
protected final double yInit;
|
||||
|
||||
public Board(Canvas canvas, double xInit, double yInit) {
|
||||
this.canvas = canvas;
|
||||
this.gc = canvas.getGraphicsContext2D();
|
||||
this.xInit = xInit;
|
||||
this.yInit = yInit;
|
||||
}
|
||||
|
||||
public void clearBoard() {
|
||||
gc.clearRect(xInit, yInit, WIDTH, HEIGHT);
|
||||
}
|
||||
|
||||
public abstract void drawBoard();
|
||||
|
||||
}
|
||||
|
@ -1,80 +1,80 @@
|
||||
package lingo.client.view;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public abstract class GameBoard extends Board {
|
||||
|
||||
/** Tracks the player's previous guess evaluations */
|
||||
protected final List<int[]> results = new ArrayList<>();
|
||||
|
||||
/** Tracks the player's score */
|
||||
protected int score;
|
||||
|
||||
public GameBoard(Canvas canvas, double xInit, double yInit) {
|
||||
super(canvas, xInit, yInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBoard() {
|
||||
super.clearBoard();
|
||||
results.clear();
|
||||
}
|
||||
|
||||
protected void drawScore() {
|
||||
double scoreX = xInit + WIDTH / 2;
|
||||
double scoreY = yInit / 2;
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillText(String.valueOf(score), scoreX, scoreY);
|
||||
}
|
||||
|
||||
protected void drawGrid() {
|
||||
gc.beginPath();
|
||||
for (int x = 0; x <= WIDTH; x += SIDE) {
|
||||
gc.moveTo(xInit + x, yInit);
|
||||
gc.lineTo(xInit + x, yInit + HEIGHT);
|
||||
}
|
||||
for (int y = 0; y <= HEIGHT; y += SIDE) {
|
||||
gc.moveTo(xInit, yInit + y);
|
||||
gc.lineTo(xInit + WIDTH, yInit + y);
|
||||
}
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.stroke();
|
||||
}
|
||||
|
||||
protected void drawResults() {
|
||||
double y = yInit + SIDE * 1.5;
|
||||
int numResults = Math.min(4, results.size());
|
||||
for (int i = 0; i < numResults; i++) {
|
||||
double x = xInit + SIDE * 0.5;
|
||||
int[] result = results.get(results.size() - numResults + i);
|
||||
for (int j = 0; j < 5; j++) {
|
||||
if (result[j] == 1) {
|
||||
gc.setFill(Color.YELLOW);
|
||||
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
|
||||
} else if (result[j] == 2) {
|
||||
gc.setFill(Color.ORANGE);
|
||||
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
|
||||
}
|
||||
x += SIDE;
|
||||
}
|
||||
y += SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
public void addResult(int[] value) {
|
||||
results.add(value);
|
||||
}
|
||||
|
||||
public void addToScore(int value) {
|
||||
score += value;
|
||||
}
|
||||
|
||||
public void clearScore() {
|
||||
score = 0;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.view;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public abstract class GameBoard extends Board {
|
||||
|
||||
/** Tracks the player's previous guess evaluations */
|
||||
protected final List<int[]> results = new ArrayList<>();
|
||||
|
||||
/** Tracks the player's score */
|
||||
protected int score;
|
||||
|
||||
public GameBoard(Canvas canvas, double xInit, double yInit) {
|
||||
super(canvas, xInit, yInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBoard() {
|
||||
super.clearBoard();
|
||||
results.clear();
|
||||
}
|
||||
|
||||
protected void drawScore() {
|
||||
double scoreX = xInit + WIDTH / 2;
|
||||
double scoreY = yInit / 2;
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.fillText(String.valueOf(score), scoreX, scoreY);
|
||||
}
|
||||
|
||||
protected void drawGrid() {
|
||||
gc.beginPath();
|
||||
for (int x = 0; x <= WIDTH; x += SIDE) {
|
||||
gc.moveTo(xInit + x, yInit);
|
||||
gc.lineTo(xInit + x, yInit + HEIGHT);
|
||||
}
|
||||
for (int y = 0; y <= HEIGHT; y += SIDE) {
|
||||
gc.moveTo(xInit, yInit + y);
|
||||
gc.lineTo(xInit + WIDTH, yInit + y);
|
||||
}
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.stroke();
|
||||
}
|
||||
|
||||
protected void drawResults() {
|
||||
double y = yInit + SIDE * 1.5;
|
||||
int numResults = Math.min(4, results.size());
|
||||
for (int i = 0; i < numResults; i++) {
|
||||
double x = xInit + SIDE * 0.5;
|
||||
int[] result = results.get(results.size() - numResults + i);
|
||||
for (int j = 0; j < 5; j++) {
|
||||
if (result[j] == 1) {
|
||||
gc.setFill(Color.YELLOW);
|
||||
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
|
||||
} else if (result[j] == 2) {
|
||||
gc.setFill(Color.ORANGE);
|
||||
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
|
||||
}
|
||||
x += SIDE;
|
||||
}
|
||||
y += SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
public void addResult(int[] value) {
|
||||
results.add(value);
|
||||
}
|
||||
|
||||
public void addToScore(int value) {
|
||||
score += value;
|
||||
}
|
||||
|
||||
public void clearScore() {
|
||||
score = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
package lingo.client.view;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
|
||||
public class OpponentBoard extends GameBoard {
|
||||
|
||||
public OpponentBoard(Canvas canvas, double xInit, double yInit) {
|
||||
super(canvas, xInit, yInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBoard() {
|
||||
drawScore();
|
||||
drawResults();
|
||||
drawGrid();
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.view;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
|
||||
public class OpponentBoard extends GameBoard {
|
||||
|
||||
public OpponentBoard(Canvas canvas, double xInit, double yInit) {
|
||||
super(canvas, xInit, yInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBoard() {
|
||||
drawScore();
|
||||
drawResults();
|
||||
drawGrid();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,120 +1,120 @@
|
||||
package lingo.client.view;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class PlayerBoard extends GameBoard {
|
||||
|
||||
/** Tracks the player's current guess */
|
||||
private final StringBuilder guess = new StringBuilder();
|
||||
|
||||
/** Tracks the player's previous guesses */
|
||||
private final List<String> guesses = new ArrayList<>();
|
||||
|
||||
/** Tracks the player's progress toward the current word */
|
||||
private final Map<Integer, String> progress = new HashMap<>();
|
||||
|
||||
public PlayerBoard(Canvas canvas, double xInit, double yInit) {
|
||||
super(canvas, xInit, yInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBoard() {
|
||||
super.clearBoard();
|
||||
guess.setLength(0);
|
||||
guesses.clear();
|
||||
progress.clear();
|
||||
results.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBoard() {
|
||||
drawScore();
|
||||
drawInput();
|
||||
drawResults();
|
||||
double yStart = drawGuesses();
|
||||
drawHint(yStart);
|
||||
drawGrid();
|
||||
}
|
||||
|
||||
private void drawInput() {
|
||||
gc.setFill(Color.GREEN);
|
||||
double x = xInit + SIDE * 0.5;
|
||||
double y = yInit + SIDE * 0.5;
|
||||
for (int i = 0; i < guess.length(); i++) {
|
||||
String character = String.valueOf(guess.charAt(i));
|
||||
gc.fillText(character, x, y);
|
||||
x += SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
private double drawGuesses() {
|
||||
double y = yInit + SIDE * 1.5;
|
||||
double numGuesses = Math.min(4, guesses.size());
|
||||
for (int i = 0; i < numGuesses; i++) {
|
||||
double x = xInit + SIDE * 0.5;
|
||||
String guess = guesses.get((int) (guesses.size() - numGuesses + i));
|
||||
for (int j = 0; j < 5; j++) {
|
||||
String character = String.valueOf(guess.charAt(j));
|
||||
gc.setFill(Color.GREEN);
|
||||
gc.fillText(character, x, y);
|
||||
x += SIDE;
|
||||
}
|
||||
y += SIDE;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
private void drawHint(double yStart) {
|
||||
double x = xInit + SIDE * 0.5;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (progress.containsKey(i)) {
|
||||
gc.fillText(progress.get(i), x, yStart);
|
||||
}
|
||||
x += SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
public void addGuess(String value) {
|
||||
guesses.add(value);
|
||||
}
|
||||
|
||||
public String getGuess() {
|
||||
return guess.toString();
|
||||
}
|
||||
|
||||
public boolean handleBackspace() {
|
||||
if (guess.length() > 0) {
|
||||
guess.setLength(guess.length() - 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String handleEnter() {
|
||||
if (guess.length() == 5) {
|
||||
final String value = guess.toString();
|
||||
guess.setLength(0);
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean handleLetter(String letter) {
|
||||
if (guess.length() < 5) {
|
||||
guess.append(letter);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setProgress(int i, char letter) {
|
||||
progress.put(i, String.valueOf(letter));
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.client.view;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class PlayerBoard extends GameBoard {
|
||||
|
||||
/** Tracks the player's current guess */
|
||||
private final StringBuilder guess = new StringBuilder();
|
||||
|
||||
/** Tracks the player's previous guesses */
|
||||
private final List<String> guesses = new ArrayList<>();
|
||||
|
||||
/** Tracks the player's progress toward the current word */
|
||||
private final Map<Integer, String> progress = new HashMap<>();
|
||||
|
||||
public PlayerBoard(Canvas canvas, double xInit, double yInit) {
|
||||
super(canvas, xInit, yInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBoard() {
|
||||
super.clearBoard();
|
||||
guess.setLength(0);
|
||||
guesses.clear();
|
||||
progress.clear();
|
||||
results.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawBoard() {
|
||||
drawScore();
|
||||
drawInput();
|
||||
drawResults();
|
||||
double yStart = drawGuesses();
|
||||
drawHint(yStart);
|
||||
drawGrid();
|
||||
}
|
||||
|
||||
private void drawInput() {
|
||||
gc.setFill(Color.GREEN);
|
||||
double x = xInit + SIDE * 0.5;
|
||||
double y = yInit + SIDE * 0.5;
|
||||
for (int i = 0; i < guess.length(); i++) {
|
||||
String character = String.valueOf(guess.charAt(i));
|
||||
gc.fillText(character, x, y);
|
||||
x += SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
private double drawGuesses() {
|
||||
double y = yInit + SIDE * 1.5;
|
||||
double numGuesses = Math.min(4, guesses.size());
|
||||
for (int i = 0; i < numGuesses; i++) {
|
||||
double x = xInit + SIDE * 0.5;
|
||||
String guess = guesses.get((int) (guesses.size() - numGuesses + i));
|
||||
for (int j = 0; j < 5; j++) {
|
||||
String character = String.valueOf(guess.charAt(j));
|
||||
gc.setFill(Color.GREEN);
|
||||
gc.fillText(character, x, y);
|
||||
x += SIDE;
|
||||
}
|
||||
y += SIDE;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
private void drawHint(double yStart) {
|
||||
double x = xInit + SIDE * 0.5;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (progress.containsKey(i)) {
|
||||
gc.fillText(progress.get(i), x, yStart);
|
||||
}
|
||||
x += SIDE;
|
||||
}
|
||||
}
|
||||
|
||||
public void addGuess(String value) {
|
||||
guesses.add(value);
|
||||
}
|
||||
|
||||
public String getGuess() {
|
||||
return guess.toString();
|
||||
}
|
||||
|
||||
public boolean handleBackspace() {
|
||||
if (guess.length() > 0) {
|
||||
guess.setLength(guess.length() - 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String handleEnter() {
|
||||
if (guess.length() == 5) {
|
||||
final String value = guess.toString();
|
||||
guess.setLength(0);
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean handleLetter(String letter) {
|
||||
if (guess.length() < 5) {
|
||||
guess.append(letter);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setProgress(int i, char letter) {
|
||||
progress.put(i, String.valueOf(letter));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,195 +1,195 @@
|
||||
h1 {
|
||||
font: 24pt sans-serif;
|
||||
font-variant: small-caps;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
/* Below based on http://tobiasahlin.com/spinkit */
|
||||
|
||||
.sk-cube-grid {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 35px auto;
|
||||
}
|
||||
|
||||
.sk-cube-grid .sk-cube {
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
background-color: orange;
|
||||
float: left;
|
||||
-webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out;
|
||||
animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/*
|
||||
0.0s - 26
|
||||
0.1s - 27, 21
|
||||
0.2s - 28, 22, 16
|
||||
0.3s - 29, 23, 17, 11
|
||||
0.4s - 30, 24, 18, 12, 06
|
||||
0.5s - 25, 19, 13, 07, 01
|
||||
0.6s - 20, 14, 08, 02
|
||||
0.7s - 15, 09, 03
|
||||
0.8s - 10, 04
|
||||
0.9s - 05
|
||||
*/
|
||||
|
||||
/* 0 second delay */
|
||||
.sk-cube-grid .sk-cube26 {
|
||||
-webkit-animation-delay: 0s;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
/* 0.1 second delay */
|
||||
.sk-cube-grid .sk-cube27 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube21 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
/* 0.2 second delay */
|
||||
.sk-cube-grid .sk-cube28 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube22 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube16 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
/* 0.3 second delay */
|
||||
.sk-cube-grid .sk-cube29 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube23 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube17 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube11 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
/* 0.4 second delay */
|
||||
.sk-cube-grid .sk-cube30 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube24 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube18 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube12 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube6 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
/* 0.5 second delay */
|
||||
.sk-cube-grid .sk-cube25 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube19 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube13 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube7 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube1 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
/* 0.6 second delay */
|
||||
.sk-cube-grid .sk-cube20 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube14 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube8 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube2 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
/* 0.7 second delay */
|
||||
.sk-cube-grid .sk-cube15 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube9 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube3 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
|
||||
/* 0.8 second delay */
|
||||
.sk-cube-grid .sk-cube10 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube4 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
/* 0.9 second delay */
|
||||
.sk-cube-grid .sk-cube5 {
|
||||
-webkit-animation-delay: 0.9s;
|
||||
animation-delay: 0.9s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-cubeGridScaleDelay {
|
||||
0%, 70%, 100% {
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-cubeGridScaleDelay {
|
||||
0%, 70%, 100% {
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
font: 24pt sans-serif;
|
||||
font-variant: small-caps;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
/* Below based on http://tobiasahlin.com/spinkit */
|
||||
|
||||
.sk-cube-grid {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 35px auto;
|
||||
}
|
||||
|
||||
.sk-cube-grid .sk-cube {
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
background-color: orange;
|
||||
float: left;
|
||||
-webkit-animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out;
|
||||
animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/*
|
||||
0.0s - 26
|
||||
0.1s - 27, 21
|
||||
0.2s - 28, 22, 16
|
||||
0.3s - 29, 23, 17, 11
|
||||
0.4s - 30, 24, 18, 12, 06
|
||||
0.5s - 25, 19, 13, 07, 01
|
||||
0.6s - 20, 14, 08, 02
|
||||
0.7s - 15, 09, 03
|
||||
0.8s - 10, 04
|
||||
0.9s - 05
|
||||
*/
|
||||
|
||||
/* 0 second delay */
|
||||
.sk-cube-grid .sk-cube26 {
|
||||
-webkit-animation-delay: 0s;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
/* 0.1 second delay */
|
||||
.sk-cube-grid .sk-cube27 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube21 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
/* 0.2 second delay */
|
||||
.sk-cube-grid .sk-cube28 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube22 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube16 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
/* 0.3 second delay */
|
||||
.sk-cube-grid .sk-cube29 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube23 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube17 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube11 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
/* 0.4 second delay */
|
||||
.sk-cube-grid .sk-cube30 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube24 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube18 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube12 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube6 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
/* 0.5 second delay */
|
||||
.sk-cube-grid .sk-cube25 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube19 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube13 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube7 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube1 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
/* 0.6 second delay */
|
||||
.sk-cube-grid .sk-cube20 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube14 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube8 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube2 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
/* 0.7 second delay */
|
||||
.sk-cube-grid .sk-cube15 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube9 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube3 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
|
||||
/* 0.8 second delay */
|
||||
.sk-cube-grid .sk-cube10 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube4 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
/* 0.9 second delay */
|
||||
.sk-cube-grid .sk-cube5 {
|
||||
-webkit-animation-delay: 0.9s;
|
||||
animation-delay: 0.9s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-cubeGridScaleDelay {
|
||||
0%, 70%, 100% {
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-cubeGridScaleDelay {
|
||||
0%, 70%, 100% {
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,39 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Waiting for Opponent</h1>
|
||||
<!-- Based on http://tobiasahlin.com/spinkit -->
|
||||
<div class="sk-cube-grid">
|
||||
<div class="sk-cube sk-cube1"></div>
|
||||
<div class="sk-cube sk-cube2"></div>
|
||||
<div class="sk-cube sk-cube3"></div>
|
||||
<div class="sk-cube sk-cube4"></div>
|
||||
<div class="sk-cube sk-cube5"></div>
|
||||
<div class="sk-cube sk-cube6"></div>
|
||||
<div class="sk-cube sk-cube7"></div>
|
||||
<div class="sk-cube sk-cube8"></div>
|
||||
<div class="sk-cube sk-cube9"></div>
|
||||
<div class="sk-cube sk-cube10"></div>
|
||||
<div class="sk-cube sk-cube11"></div>
|
||||
<div class="sk-cube sk-cube12"></div>
|
||||
<div class="sk-cube sk-cube13"></div>
|
||||
<div class="sk-cube sk-cube14"></div>
|
||||
<div class="sk-cube sk-cube15"></div>
|
||||
<div class="sk-cube sk-cube16"></div>
|
||||
<div class="sk-cube sk-cube17"></div>
|
||||
<div class="sk-cube sk-cube18"></div>
|
||||
<div class="sk-cube sk-cube19"></div>
|
||||
<div class="sk-cube sk-cube20"></div>
|
||||
<div class="sk-cube sk-cube21"></div>
|
||||
<div class="sk-cube sk-cube22"></div>
|
||||
<div class="sk-cube sk-cube23"></div>
|
||||
<div class="sk-cube sk-cube24"></div>
|
||||
<div class="sk-cube sk-cube25"></div>
|
||||
<div class="sk-cube sk-cube26"></div>
|
||||
<div class="sk-cube sk-cube27"></div>
|
||||
<div class="sk-cube sk-cube28"></div>
|
||||
<div class="sk-cube sk-cube29"></div>
|
||||
<div class="sk-cube sk-cube30"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Waiting for Opponent</h1>
|
||||
<!-- Based on http://tobiasahlin.com/spinkit -->
|
||||
<div class="sk-cube-grid">
|
||||
<div class="sk-cube sk-cube1"></div>
|
||||
<div class="sk-cube sk-cube2"></div>
|
||||
<div class="sk-cube sk-cube3"></div>
|
||||
<div class="sk-cube sk-cube4"></div>
|
||||
<div class="sk-cube sk-cube5"></div>
|
||||
<div class="sk-cube sk-cube6"></div>
|
||||
<div class="sk-cube sk-cube7"></div>
|
||||
<div class="sk-cube sk-cube8"></div>
|
||||
<div class="sk-cube sk-cube9"></div>
|
||||
<div class="sk-cube sk-cube10"></div>
|
||||
<div class="sk-cube sk-cube11"></div>
|
||||
<div class="sk-cube sk-cube12"></div>
|
||||
<div class="sk-cube sk-cube13"></div>
|
||||
<div class="sk-cube sk-cube14"></div>
|
||||
<div class="sk-cube sk-cube15"></div>
|
||||
<div class="sk-cube sk-cube16"></div>
|
||||
<div class="sk-cube sk-cube17"></div>
|
||||
<div class="sk-cube sk-cube18"></div>
|
||||
<div class="sk-cube sk-cube19"></div>
|
||||
<div class="sk-cube sk-cube20"></div>
|
||||
<div class="sk-cube sk-cube21"></div>
|
||||
<div class="sk-cube sk-cube22"></div>
|
||||
<div class="sk-cube sk-cube23"></div>
|
||||
<div class="sk-cube sk-cube24"></div>
|
||||
<div class="sk-cube sk-cube25"></div>
|
||||
<div class="sk-cube sk-cube26"></div>
|
||||
<div class="sk-cube sk-cube27"></div>
|
||||
<div class="sk-cube sk-cube28"></div>
|
||||
<div class="sk-cube sk-cube29"></div>
|
||||
<div class="sk-cube sk-cube30"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,38 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.geometry.Pos?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.geometry.Pos?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<BorderPane xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="lingo.client.bootstrap.LingoPresenter"
|
||||
fx:id="content"
|
||||
prefWidth="650"
|
||||
prefHeight="420">
|
||||
|
||||
<center>
|
||||
<BorderPane fx:id="gameModeChooser">
|
||||
<center>
|
||||
<VBox spacing="20" alignment="CENTER">
|
||||
<children>
|
||||
<Button text="Practice" onAction="#showSinglePlayer" prefWidth="350" styleClass="game-mode" />
|
||||
<Button text="Multiplayer" onAction="#showMultiplayer" prefWidth="350" styleClass="game-mode" />
|
||||
</children>
|
||||
</VBox>
|
||||
</center>
|
||||
<bottom>
|
||||
<Button text="Exit" onAction="#exit" prefWidth="50" styleClass="game-nav">
|
||||
<BorderPane.alignment>
|
||||
<Pos fx:value="BOTTOM_LEFT" />
|
||||
</BorderPane.alignment>
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="10" left="10" />
|
||||
</BorderPane.margin>
|
||||
</Button>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</center>
|
||||
|
||||
</BorderPane>
|
||||
|
||||
<BorderPane xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="lingo.client.bootstrap.LingoPresenter"
|
||||
fx:id="content"
|
||||
prefWidth="650"
|
||||
prefHeight="420">
|
||||
|
||||
<center>
|
||||
<BorderPane fx:id="gameModeChooser">
|
||||
<center>
|
||||
<VBox spacing="20" alignment="CENTER">
|
||||
<children>
|
||||
<Button text="Practice" onAction="#showSinglePlayer" prefWidth="350" styleClass="game-mode" />
|
||||
<Button text="Multiplayer" onAction="#showMultiplayer" prefWidth="350" styleClass="game-mode" />
|
||||
</children>
|
||||
</VBox>
|
||||
</center>
|
||||
<bottom>
|
||||
<Button text="Exit" onAction="#exit" prefWidth="50" styleClass="game-nav">
|
||||
<BorderPane.alignment>
|
||||
<Pos fx:value="BOTTOM_LEFT" />
|
||||
</BorderPane.alignment>
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="10" left="10" />
|
||||
</BorderPane.margin>
|
||||
</Button>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</center>
|
||||
|
||||
</BorderPane>
|
||||
|
@ -1,27 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.geometry.Pos?>
|
||||
<?import javafx.scene.canvas.Canvas?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.geometry.Pos?>
|
||||
<?import javafx.scene.canvas.Canvas?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.web.WebView?>
|
||||
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="lingo.client.multiplayer.MultiplayerPresenter"
|
||||
fx:id="contentPane">
|
||||
|
||||
<children>
|
||||
<Canvas fx:id="canvas" width="650" height="420" onKeyPressed="#keyPressed" />
|
||||
<WebView fx:id="webView" prefWidth="650" prefHeight="420" />
|
||||
<Button fx:id="backButton" text="Back" prefWidth="50" styleClass="game-nav">
|
||||
<StackPane.alignment>
|
||||
<Pos fx:value="BOTTOM_LEFT" />
|
||||
</StackPane.alignment>
|
||||
<StackPane.margin>
|
||||
<Insets bottom="10" left="10" />
|
||||
</StackPane.margin>
|
||||
</Button>
|
||||
</children>
|
||||
|
||||
</StackPane>
|
||||
<?import javafx.scene.web.WebView?>
|
||||
|
||||
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="lingo.client.multiplayer.MultiplayerPresenter"
|
||||
fx:id="contentPane">
|
||||
|
||||
<children>
|
||||
<Canvas fx:id="canvas" width="650" height="420" onKeyPressed="#keyPressed" />
|
||||
<WebView fx:id="webView" prefWidth="650" prefHeight="420" />
|
||||
<Button fx:id="backButton" text="Back" prefWidth="50" styleClass="game-nav">
|
||||
<StackPane.alignment>
|
||||
<Pos fx:value="BOTTOM_LEFT" />
|
||||
</StackPane.alignment>
|
||||
<StackPane.margin>
|
||||
<Insets bottom="10" left="10" />
|
||||
</StackPane.margin>
|
||||
</Button>
|
||||
</children>
|
||||
|
||||
</StackPane>
|
||||
|
@ -1,12 +1,12 @@
|
||||
.text {
|
||||
-fx-font-family: Helvetica;
|
||||
-fx-font-smoothing-type: gray;
|
||||
}
|
||||
|
||||
.game-mode {
|
||||
-fx-font-size: 24px;
|
||||
}
|
||||
|
||||
.game-nav {
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.text {
|
||||
-fx-font-family: Helvetica;
|
||||
-fx-font-smoothing-type: gray;
|
||||
}
|
||||
|
||||
.game-mode {
|
||||
-fx-font-size: 24px;
|
||||
}
|
||||
|
||||
.game-nav {
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-common</artifactId>
|
||||
<name>Lingo WebSocket :: Common</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-common</artifactId>
|
||||
<name>Lingo WebSocket :: Common</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -1,32 +1,32 @@
|
||||
package lingo.common;
|
||||
|
||||
public class ChatMessage {
|
||||
|
||||
private String username;
|
||||
|
||||
private String message;
|
||||
|
||||
public ChatMessage() {}
|
||||
|
||||
public ChatMessage(String username, String message) {
|
||||
this.username = username;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
public class ChatMessage {
|
||||
|
||||
private String username;
|
||||
|
||||
private String message;
|
||||
|
||||
public ChatMessage() {}
|
||||
|
||||
public ChatMessage(String username, String message) {
|
||||
this.username = username;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,137 +1,137 @@
|
||||
package lingo.common;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Game {
|
||||
|
||||
public static final int INCORRECT_CHARACTER = 0;
|
||||
public static final int INCORRECT_POSITION = 1;
|
||||
public static final int CORRECT_CHARACTER = 2;
|
||||
public static final int WORD_LENGTH = 5;
|
||||
|
||||
/** Nein nein nein nein nein! */
|
||||
private static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 };
|
||||
|
||||
private static final AtomicInteger idCounter = new AtomicInteger(0);
|
||||
|
||||
private int id;
|
||||
|
||||
private Player playerOne;
|
||||
|
||||
private Player playerTwo;
|
||||
|
||||
private Set<String> acceptableGuesses;
|
||||
|
||||
private List<String> possibleWords;
|
||||
|
||||
private String word;
|
||||
|
||||
private int wordIndex = 0;
|
||||
|
||||
public Game() {
|
||||
// Empty constructor required for serialization
|
||||
}
|
||||
|
||||
public Game(Player host) {
|
||||
this.id = idCounter.incrementAndGet();
|
||||
this.playerOne = host;
|
||||
}
|
||||
|
||||
private static int indexOf(char[] array, char searchTerm) {
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (array[i] == searchTerm) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean isCorrect(int[] result) {
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (result[i] != CORRECT_CHARACTER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isInvalid(int[] result) {
|
||||
return Arrays.equals(result, INVALID_GUESS);
|
||||
}
|
||||
|
||||
public int[] evaluate(String guess) {
|
||||
if (!acceptableGuesses.contains(guess)) {
|
||||
return INVALID_GUESS;
|
||||
}
|
||||
|
||||
// the guess is acceptable
|
||||
int[] result = new int[WORD_LENGTH];
|
||||
char[] remaining = new char[WORD_LENGTH];
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (guess.charAt(i) == word.charAt(i)) {
|
||||
result[i] = CORRECT_CHARACTER;
|
||||
} else {
|
||||
result[i] = INCORRECT_CHARACTER;
|
||||
remaining[i] = word.charAt(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (result[i] == INCORRECT_CHARACTER) {
|
||||
int index = indexOf(remaining, guess.charAt(i));
|
||||
if (index != -1) {
|
||||
result[i] = INCORRECT_POSITION;
|
||||
remaining[index] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Player getPlayerOne() {
|
||||
return playerOne;
|
||||
}
|
||||
|
||||
public Player getPlayerTwo() {
|
||||
return playerTwo;
|
||||
}
|
||||
|
||||
public String newGame() {
|
||||
Collections.shuffle(possibleWords);
|
||||
wordIndex = 0;
|
||||
return newWord();
|
||||
}
|
||||
|
||||
public String newWord() {
|
||||
word = possibleWords.get(wordIndex++);
|
||||
return word;
|
||||
}
|
||||
|
||||
public void setAcceptableGuesses(Set<String> value) {
|
||||
this.acceptableGuesses = value;
|
||||
}
|
||||
|
||||
public void setId(int value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
public void setPlayerOne(Player value) {
|
||||
this.playerOne = value;
|
||||
}
|
||||
|
||||
public void setPlayerTwo(Player value) {
|
||||
this.playerTwo = value;
|
||||
}
|
||||
|
||||
public void setPossibleWords(List<String> value) {
|
||||
this.possibleWords = value;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Game {
|
||||
|
||||
public static final int INCORRECT_CHARACTER = 0;
|
||||
public static final int INCORRECT_POSITION = 1;
|
||||
public static final int CORRECT_CHARACTER = 2;
|
||||
public static final int WORD_LENGTH = 5;
|
||||
|
||||
/** Nein nein nein nein nein! */
|
||||
private static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 };
|
||||
|
||||
private static final AtomicInteger idCounter = new AtomicInteger(0);
|
||||
|
||||
private int id;
|
||||
|
||||
private Player playerOne;
|
||||
|
||||
private Player playerTwo;
|
||||
|
||||
private Set<String> acceptableGuesses;
|
||||
|
||||
private List<String> possibleWords;
|
||||
|
||||
private String word;
|
||||
|
||||
private int wordIndex = 0;
|
||||
|
||||
public Game() {
|
||||
// Empty constructor required for serialization
|
||||
}
|
||||
|
||||
public Game(Player host) {
|
||||
this.id = idCounter.incrementAndGet();
|
||||
this.playerOne = host;
|
||||
}
|
||||
|
||||
private static int indexOf(char[] array, char searchTerm) {
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (array[i] == searchTerm) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean isCorrect(int[] result) {
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (result[i] != CORRECT_CHARACTER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isInvalid(int[] result) {
|
||||
return Arrays.equals(result, INVALID_GUESS);
|
||||
}
|
||||
|
||||
public int[] evaluate(String guess) {
|
||||
if (!acceptableGuesses.contains(guess)) {
|
||||
return INVALID_GUESS;
|
||||
}
|
||||
|
||||
// the guess is acceptable
|
||||
int[] result = new int[WORD_LENGTH];
|
||||
char[] remaining = new char[WORD_LENGTH];
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (guess.charAt(i) == word.charAt(i)) {
|
||||
result[i] = CORRECT_CHARACTER;
|
||||
} else {
|
||||
result[i] = INCORRECT_CHARACTER;
|
||||
remaining[i] = word.charAt(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < WORD_LENGTH; i++) {
|
||||
if (result[i] == INCORRECT_CHARACTER) {
|
||||
int index = indexOf(remaining, guess.charAt(i));
|
||||
if (index != -1) {
|
||||
result[i] = INCORRECT_POSITION;
|
||||
remaining[index] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Player getPlayerOne() {
|
||||
return playerOne;
|
||||
}
|
||||
|
||||
public Player getPlayerTwo() {
|
||||
return playerTwo;
|
||||
}
|
||||
|
||||
public String newGame() {
|
||||
Collections.shuffle(possibleWords);
|
||||
wordIndex = 0;
|
||||
return newWord();
|
||||
}
|
||||
|
||||
public String newWord() {
|
||||
word = possibleWords.get(wordIndex++);
|
||||
return word;
|
||||
}
|
||||
|
||||
public void setAcceptableGuesses(Set<String> value) {
|
||||
this.acceptableGuesses = value;
|
||||
}
|
||||
|
||||
public void setId(int value) {
|
||||
this.id = value;
|
||||
}
|
||||
|
||||
public void setPlayerOne(Player value) {
|
||||
this.playerOne = value;
|
||||
}
|
||||
|
||||
public void setPlayerTwo(Player value) {
|
||||
this.playerTwo = value;
|
||||
}
|
||||
|
||||
public void setPossibleWords(List<String> value) {
|
||||
this.possibleWords = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,32 +1,32 @@
|
||||
package lingo.common;
|
||||
|
||||
public class GameLeftMessage {
|
||||
|
||||
private Game game;
|
||||
|
||||
private Player gameLeaver;
|
||||
|
||||
public GameLeftMessage() {}
|
||||
|
||||
public GameLeftMessage(Game game, Player gameLeaver) {
|
||||
this.game = game;
|
||||
this.gameLeaver = gameLeaver;
|
||||
}
|
||||
|
||||
public Game getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public Player getGameLeaver() {
|
||||
return gameLeaver;
|
||||
}
|
||||
|
||||
public void setGame(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public void setGameLeaver(Player gameLeaver) {
|
||||
this.gameLeaver = gameLeaver;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
public class GameLeftMessage {
|
||||
|
||||
private Game game;
|
||||
|
||||
private Player gameLeaver;
|
||||
|
||||
public GameLeftMessage() {}
|
||||
|
||||
public GameLeftMessage(Game game, Player gameLeaver) {
|
||||
this.game = game;
|
||||
this.gameLeaver = gameLeaver;
|
||||
}
|
||||
|
||||
public Game getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public Player getGameLeaver() {
|
||||
return gameLeaver;
|
||||
}
|
||||
|
||||
public void setGame(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public void setGameLeaver(Player gameLeaver) {
|
||||
this.gameLeaver = gameLeaver;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,40 +1,40 @@
|
||||
package lingo.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public class Player {
|
||||
|
||||
@JsonIgnore
|
||||
private String sessionId;
|
||||
|
||||
private String username;
|
||||
|
||||
public Player() {
|
||||
// Empty constructor required for serialization
|
||||
}
|
||||
|
||||
public Player(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String value) {
|
||||
this.username = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (username != null) {
|
||||
return username;
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public class Player {
|
||||
|
||||
@JsonIgnore
|
||||
private String sessionId;
|
||||
|
||||
private String username;
|
||||
|
||||
public Player() {
|
||||
// Empty constructor required for serialization
|
||||
}
|
||||
|
||||
public Player(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String value) {
|
||||
this.username = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (username != null) {
|
||||
return username;
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,47 +1,47 @@
|
||||
package lingo.common;
|
||||
|
||||
public class Report {
|
||||
|
||||
private boolean correct;
|
||||
|
||||
private String firstLetter;
|
||||
|
||||
private String guess;
|
||||
|
||||
private int[] result;
|
||||
|
||||
public Report() {}
|
||||
|
||||
public String getFirstLetter() {
|
||||
return firstLetter;
|
||||
}
|
||||
|
||||
public void setFirstLetter(String value) {
|
||||
this.firstLetter = value;
|
||||
}
|
||||
|
||||
public String getGuess() {
|
||||
return guess;
|
||||
}
|
||||
|
||||
public void setGuess(String value) {
|
||||
this.guess = value;
|
||||
}
|
||||
|
||||
public int[] getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(int[] value) {
|
||||
this.result = value;
|
||||
}
|
||||
|
||||
public boolean isCorrect() {
|
||||
return correct;
|
||||
}
|
||||
|
||||
public void setCorrect(boolean value) {
|
||||
this.correct = value;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
public class Report {
|
||||
|
||||
private boolean correct;
|
||||
|
||||
private String firstLetter;
|
||||
|
||||
private String guess;
|
||||
|
||||
private int[] result;
|
||||
|
||||
public Report() {}
|
||||
|
||||
public String getFirstLetter() {
|
||||
return firstLetter;
|
||||
}
|
||||
|
||||
public void setFirstLetter(String value) {
|
||||
this.firstLetter = value;
|
||||
}
|
||||
|
||||
public String getGuess() {
|
||||
return guess;
|
||||
}
|
||||
|
||||
public void setGuess(String value) {
|
||||
this.guess = value;
|
||||
}
|
||||
|
||||
public int[] getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(int[] value) {
|
||||
this.result = value;
|
||||
}
|
||||
|
||||
public boolean isCorrect() {
|
||||
return correct;
|
||||
}
|
||||
|
||||
public void setCorrect(boolean value) {
|
||||
this.correct = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,43 +1,43 @@
|
||||
package lingo.common;
|
||||
|
||||
public class SetUsernameMessage {
|
||||
|
||||
private String errorMessage;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private String username;
|
||||
|
||||
public SetUsernameMessage() {}
|
||||
|
||||
public SetUsernameMessage(boolean success, String username, String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
this.success = success;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
public class SetUsernameMessage {
|
||||
|
||||
private String errorMessage;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private String username;
|
||||
|
||||
public SetUsernameMessage() {}
|
||||
|
||||
public SetUsernameMessage(boolean success, String username, String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
this.success = success;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,38 +1,38 @@
|
||||
package lingo.common;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class WordReader {
|
||||
|
||||
private static void readFileToCollection(String filename, Collection<String> c) throws IOException {
|
||||
try (final InputStream stream = WordReader.class.getResourceAsStream(filename);
|
||||
final InputStreamReader streamReader = new InputStreamReader(stream);
|
||||
final BufferedReader bufferedReader = new BufferedReader(streamReader)) {
|
||||
String line = null;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
c.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> readFileToList(String filename) throws IOException {
|
||||
final List<String> list = new ArrayList<>();
|
||||
readFileToCollection(filename, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Set<String> readFileToSet(String filename) throws IOException {
|
||||
final Set<String> list = new HashSet<>();
|
||||
readFileToCollection(filename, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.common;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class WordReader {
|
||||
|
||||
private static void readFileToCollection(String filename, Collection<String> c) throws IOException {
|
||||
try (final InputStream stream = WordReader.class.getResourceAsStream(filename);
|
||||
final InputStreamReader streamReader = new InputStreamReader(stream);
|
||||
final BufferedReader bufferedReader = new BufferedReader(streamReader)) {
|
||||
String line = null;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
c.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> readFileToList(String filename) throws IOException {
|
||||
final List<String> list = new ArrayList<>();
|
||||
readFileToCollection(filename, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Set<String> readFileToSet(String filename) throws IOException {
|
||||
final Set<String> list = new HashSet<>();
|
||||
readFileToCollection(filename, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
80
pom.xml
80
pom.xml
@ -1,40 +1,40 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.10.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Lingo WebSocket</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>client-api</module>
|
||||
<module>common</module>
|
||||
<module>server</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<!-- Prevent client module from building on Heroku -->
|
||||
<!-- Heroku's JDKs are headless -->
|
||||
<profile>
|
||||
<id>javafx</id>
|
||||
<modules>
|
||||
<module>client</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.10.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Lingo WebSocket</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>client-api</module>
|
||||
<module>common</module>
|
||||
<module>server</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<!-- Prevent client module from building on Heroku -->
|
||||
<!-- Heroku's JDKs are headless -->
|
||||
<profile>
|
||||
<id>javafx</id>
|
||||
<modules>
|
||||
<module>client</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
128
server/pom.xml
128
server/pom.xml
@ -1,64 +1,64 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-server</artifactId>
|
||||
<name>Lingo WebSocket :: Server</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>lingo-websocket-client-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Development Tools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<!-- Prevent transitive application to other modules -->
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- Enable hot refreshing of resources -->
|
||||
<!-- Add src/main/resources to the classpath -->
|
||||
<!-- Remove duplicate resources from target/classes -->
|
||||
<addResources>true</addResources>
|
||||
<!-- Add src/main/config to the classpath -->
|
||||
<folders>src/main/config</folders>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- Repackage as executable JAR (java -jar) -->
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.charego</groupId>
|
||||
<artifactId>lingo-websocket</artifactId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lingo-websocket-server</artifactId>
|
||||
<name>Lingo WebSocket :: Server</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>lingo-websocket-client-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Development Tools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<!-- Prevent transitive application to other modules -->
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- Enable hot refreshing of resources -->
|
||||
<!-- Add src/main/resources to the classpath -->
|
||||
<!-- Remove duplicate resources from target/classes -->
|
||||
<addResources>true</addResources>
|
||||
<!-- Add src/main/config to the classpath -->
|
||||
<folders>src/main/config</folders>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- Repackage as executable JAR (java -jar) -->
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -1,20 +1,20 @@
|
||||
package lingo.server;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LingoServer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Heroku dynamically assigns a port
|
||||
final String webPort = System.getenv("PORT");
|
||||
if (webPort != null && !webPort.isEmpty()) {
|
||||
System.setProperty("server.port", webPort);
|
||||
}
|
||||
|
||||
SpringApplication.run(LingoServer.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.server;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LingoServer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Heroku dynamically assigns a port
|
||||
final String webPort = System.getenv("PORT");
|
||||
if (webPort != null && !webPort.isEmpty()) {
|
||||
System.setProperty("server.port", webPort);
|
||||
}
|
||||
|
||||
SpringApplication.run(LingoServer.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,85 +1,85 @@
|
||||
package lingo.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.messaging.AbstractSubProtocolEvent;
|
||||
import org.springframework.web.socket.messaging.SessionConnectedEvent;
|
||||
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
|
||||
|
||||
import lingo.common.Player;
|
||||
|
||||
@Component
|
||||
public class SessionManager implements ApplicationListener<AbstractSubProtocolEvent> {
|
||||
|
||||
public interface Listener {
|
||||
void playerJoined(Player player);
|
||||
void playerLeft(Player player);
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||
|
||||
private final Map<String, Player> playerBySession = new HashMap<>();
|
||||
|
||||
private final Set<Listener> listeners = new HashSet<>();
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public Player getPlayer(String sessionId) {
|
||||
return playerBySession.get(sessionId);
|
||||
}
|
||||
|
||||
public int getPlayerCount() {
|
||||
return playerBySession.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSubProtocolEvent event) {
|
||||
if (event instanceof SessionConnectedEvent) {
|
||||
onSessionConnected((SessionConnectedEvent) event);
|
||||
} else if (event instanceof SessionDisconnectEvent) {
|
||||
onSessionDisconnect((SessionDisconnectEvent) event);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSessionConnected(SessionConnectedEvent event) {
|
||||
final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId();
|
||||
final Player player = new Player(sessionId);
|
||||
log.info("Player connected: {}", player);
|
||||
playerBySession.put(sessionId, player);
|
||||
synchronized (listeners) {
|
||||
for (Listener listener : listeners) {
|
||||
listener.playerJoined(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onSessionDisconnect(SessionDisconnectEvent event) {
|
||||
final String sessionId = event.getSessionId();
|
||||
final Player player = playerBySession.remove(sessionId);
|
||||
log.info("Player disconnected: {}", player);
|
||||
synchronized (listeners) {
|
||||
for (Listener listener : listeners) {
|
||||
listener.playerLeft(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.messaging.AbstractSubProtocolEvent;
|
||||
import org.springframework.web.socket.messaging.SessionConnectedEvent;
|
||||
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
|
||||
|
||||
import lingo.common.Player;
|
||||
|
||||
@Component
|
||||
public class SessionManager implements ApplicationListener<AbstractSubProtocolEvent> {
|
||||
|
||||
public interface Listener {
|
||||
void playerJoined(Player player);
|
||||
void playerLeft(Player player);
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||
|
||||
private final Map<String, Player> playerBySession = new HashMap<>();
|
||||
|
||||
private final Set<Listener> listeners = new HashSet<>();
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public Player getPlayer(String sessionId) {
|
||||
return playerBySession.get(sessionId);
|
||||
}
|
||||
|
||||
public int getPlayerCount() {
|
||||
return playerBySession.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSubProtocolEvent event) {
|
||||
if (event instanceof SessionConnectedEvent) {
|
||||
onSessionConnected((SessionConnectedEvent) event);
|
||||
} else if (event instanceof SessionDisconnectEvent) {
|
||||
onSessionDisconnect((SessionDisconnectEvent) event);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSessionConnected(SessionConnectedEvent event) {
|
||||
final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId();
|
||||
final Player player = new Player(sessionId);
|
||||
log.info("Player connected: {}", player);
|
||||
playerBySession.put(sessionId, player);
|
||||
synchronized (listeners) {
|
||||
for (Listener listener : listeners) {
|
||||
listener.playerJoined(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onSessionDisconnect(SessionDisconnectEvent event) {
|
||||
final String sessionId = event.getSessionId();
|
||||
final Player player = playerBySession.remove(sessionId);
|
||||
log.info("Player disconnected: {}", player);
|
||||
synchronized (listeners) {
|
||||
for (Listener listener : listeners) {
|
||||
listener.playerLeft(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
synchronized (listeners) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,39 +1,39 @@
|
||||
package lingo.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lingo.common.WordReader;
|
||||
|
||||
@Component
|
||||
public class WordRepository {
|
||||
|
||||
private final Set<String> guesses;
|
||||
|
||||
private final List<String> words;
|
||||
|
||||
public WordRepository() throws IOException {
|
||||
guesses = WordReader.readFileToSet("/guesses.txt");
|
||||
words = WordReader.readFileToList("/words.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of acceptable guesses (unmodifiable).
|
||||
*/
|
||||
public Set<String> getGuesses() {
|
||||
return Collections.unmodifiableSet(guesses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the list of potential answers (OK to shuffle it).
|
||||
*/
|
||||
public List<String> getWords() {
|
||||
return new ArrayList<>(words);
|
||||
}
|
||||
|
||||
}
|
||||
package lingo.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lingo.common.WordReader;
|
||||
|
||||
@Component
|
||||
public class WordRepository {
|
||||
|
||||
private final Set<String> guesses;
|
||||
|
||||
private final List<String> words;
|
||||
|
||||
public WordRepository() throws IOException {
|
||||
guesses = WordReader.readFileToSet("/guesses.txt");
|
||||
words = WordReader.readFileToList("/words.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of acceptable guesses (unmodifiable).
|
||||
*/
|
||||
public Set<String> getGuesses() {
|
||||
return Collections.unmodifiableSet(guesses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the list of potential answers (OK to shuffle it).
|
||||
*/
|
||||
public List<String> getWords() {
|
||||
return new ArrayList<>(words);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,200 +1,200 @@
|
||||
/* Based on http://tobiasahlin.com/spinkit */
|
||||
|
||||
.sk-cube-grid {
|
||||
width: 200px; /* Should be 5x the width below */
|
||||
height: 240px; /* Should be 6x the height below */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sk-cube-grid .sk-cube {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
float: left;
|
||||
-webkit-animation: sk-cubeGridScaleDelay 2s infinite ease-in-out;
|
||||
animation: sk-cubeGridScaleDelay 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/*
|
||||
0.0s - 26
|
||||
0.1s - 27, 21
|
||||
0.2s - 28, 22, 16
|
||||
0.3s - 29, 23, 17, 11
|
||||
0.4s - 30, 24, 18, 12, 06
|
||||
0.5s - 25, 19, 13, 07, 01
|
||||
0.6s - 20, 14, 08, 02
|
||||
0.7s - 15, 09, 03
|
||||
0.8s - 10, 04
|
||||
0.9s - 05
|
||||
*/
|
||||
|
||||
/* 0 second delay */
|
||||
.sk-cube-grid .sk-cube26 {
|
||||
-webkit-animation-delay: 0s;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
/* 0.1 second delay */
|
||||
.sk-cube-grid .sk-cube27 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube21 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
/* 0.2 second delay */
|
||||
.sk-cube-grid .sk-cube28 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube22 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube16 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
/* 0.3 second delay */
|
||||
.sk-cube-grid .sk-cube29 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube23 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube17 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube11 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
/* 0.4 second delay */
|
||||
.sk-cube-grid .sk-cube30 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube24 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube18 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube12 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube6 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
/* 0.5 second delay */
|
||||
.sk-cube-grid .sk-cube25 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube19 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube13 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube7 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube1 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
/* 0.6 second delay */
|
||||
.sk-cube-grid .sk-cube20 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube14 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube8 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube2 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
/* 0.7 second delay */
|
||||
.sk-cube-grid .sk-cube15 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube9 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube3 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
|
||||
/* 0.8 second delay */
|
||||
.sk-cube-grid .sk-cube10 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube4 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
/* 0.9 second delay */
|
||||
.sk-cube-grid .sk-cube5 {
|
||||
-webkit-animation-delay: 0.9s;
|
||||
animation-delay: 0.9s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-cubeGridScaleDelay {
|
||||
0%, 100% {
|
||||
background-color: orange;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 70% {
|
||||
background-color: yellow;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
background-color: greenyellow;
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-cubeGridScaleDelay {
|
||||
0%, 100% {
|
||||
background-color: orange;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 70% {
|
||||
background-color: yellow;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
background-color: greenyellow;
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
/* Based on http://tobiasahlin.com/spinkit */
|
||||
|
||||
.sk-cube-grid {
|
||||
width: 200px; /* Should be 5x the width below */
|
||||
height: 240px; /* Should be 6x the height below */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sk-cube-grid .sk-cube {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
float: left;
|
||||
-webkit-animation: sk-cubeGridScaleDelay 2s infinite ease-in-out;
|
||||
animation: sk-cubeGridScaleDelay 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/*
|
||||
0.0s - 26
|
||||
0.1s - 27, 21
|
||||
0.2s - 28, 22, 16
|
||||
0.3s - 29, 23, 17, 11
|
||||
0.4s - 30, 24, 18, 12, 06
|
||||
0.5s - 25, 19, 13, 07, 01
|
||||
0.6s - 20, 14, 08, 02
|
||||
0.7s - 15, 09, 03
|
||||
0.8s - 10, 04
|
||||
0.9s - 05
|
||||
*/
|
||||
|
||||
/* 0 second delay */
|
||||
.sk-cube-grid .sk-cube26 {
|
||||
-webkit-animation-delay: 0s;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
/* 0.1 second delay */
|
||||
.sk-cube-grid .sk-cube27 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube21 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
/* 0.2 second delay */
|
||||
.sk-cube-grid .sk-cube28 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube22 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube16 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
/* 0.3 second delay */
|
||||
.sk-cube-grid .sk-cube29 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube23 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube17 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube11 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
/* 0.4 second delay */
|
||||
.sk-cube-grid .sk-cube30 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube24 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube18 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube12 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube6 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
/* 0.5 second delay */
|
||||
.sk-cube-grid .sk-cube25 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube19 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube13 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube7 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube1 {
|
||||
-webkit-animation-delay: 0.5s;
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
/* 0.6 second delay */
|
||||
.sk-cube-grid .sk-cube20 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube14 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube8 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube2 {
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
/* 0.7 second delay */
|
||||
.sk-cube-grid .sk-cube15 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube9 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube3 {
|
||||
-webkit-animation-delay: 0.7s;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
|
||||
/* 0.8 second delay */
|
||||
.sk-cube-grid .sk-cube10 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
.sk-cube-grid .sk-cube4 {
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
/* 0.9 second delay */
|
||||
.sk-cube-grid .sk-cube5 {
|
||||
-webkit-animation-delay: 0.9s;
|
||||
animation-delay: 0.9s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-cubeGridScaleDelay {
|
||||
0%, 100% {
|
||||
background-color: orange;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 70% {
|
||||
background-color: yellow;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
background-color: greenyellow;
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-cubeGridScaleDelay {
|
||||
0%, 100% {
|
||||
background-color: orange;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 70% {
|
||||
background-color: yellow;
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
} 35% {
|
||||
background-color: greenyellow;
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lingo</title>
|
||||
<link rel="stylesheet" href="layout.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="vue-app" v-cloak>
|
||||
<div class="main column">
|
||||
<div class="header row">Lingo</div>
|
||||
<div class="body row nofooter">
|
||||
<div v-show="!username" class="form">
|
||||
<h2>What is your name?</h2>
|
||||
<input id="nicknameInput" type="text" class="form-control" maxlength="16" autofocus>
|
||||
<p class="error-message">{{ usernameError }}</p>
|
||||
</div>
|
||||
<div v-show="username">
|
||||
<div v-bind:class="{ primary: !inStartedGame }" class="lobby column">
|
||||
<div class="body row noheader nofooter scroll-y">
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Games</h3>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div v-if="games.length === 0" class="list-group-item">There are no games</div>
|
||||
<template v-for="game in games">
|
||||
<div v-if="game.started" v-bind:id="'game-' + game.id" class="list-group-item">
|
||||
<strong>{{ game.playerOne }}</strong> vs. <strong>{{ game.playerTwo }}</strong>
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div v-bind:class="{ primary: inStartedGame }" class="game column">
|
||||
<div class="body row noheader nofooter">
|
||||
<div v-show="inStartedGame" @keydown="onCanvasKeydown" @keypress="onCanvasKeypress">
|
||||
<canvas id="canvas" class="centered" width="600" height="475" tabindex="1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat column">
|
||||
<div class="header row">Chat</div>
|
||||
<div v-autoscroll class="body row scroll-y">
|
||||
<div v-for="message in messages" class="message-item" :class="{ log: !message.sender }">
|
||||
<strong v-if="message.sender">{{ message.sender }}</strong>
|
||||
<span>{{ message.body }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer row">
|
||||
<textarea @keypress="onChatKeypress" placeholder="Send a message" tabindex="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- https://github.com/stomp-js/stomp-websocket -->
|
||||
<script src="stomp-3.1.1.min.js"></script>
|
||||
<script src="client.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lingo</title>
|
||||
<link rel="stylesheet" href="layout.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="vue-app" v-cloak>
|
||||
<div class="main column">
|
||||
<div class="header row">Lingo</div>
|
||||
<div class="body row nofooter">
|
||||
<div v-show="!username" class="form">
|
||||
<h2>What is your name?</h2>
|
||||
<input id="nicknameInput" type="text" class="form-control" maxlength="16" autofocus>
|
||||
<p class="error-message">{{ usernameError }}</p>
|
||||
</div>
|
||||
<div v-show="username">
|
||||
<div v-bind:class="{ primary: !inStartedGame }" class="lobby column">
|
||||
<div class="body row noheader nofooter scroll-y">
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Games</h3>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div v-if="games.length === 0" class="list-group-item">There are no games</div>
|
||||
<template v-for="game in games">
|
||||
<div v-if="game.started" v-bind:id="'game-' + game.id" class="list-group-item">
|
||||
<strong>{{ game.playerOne }}</strong> vs. <strong>{{ game.playerTwo }}</strong>
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div v-bind:class="{ primary: inStartedGame }" class="game column">
|
||||
<div class="body row noheader nofooter">
|
||||
<div v-show="inStartedGame" @keydown="onCanvasKeydown" @keypress="onCanvasKeypress">
|
||||
<canvas id="canvas" class="centered" width="600" height="475" tabindex="1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat column">
|
||||
<div class="header row">Chat</div>
|
||||
<div v-autoscroll class="body row scroll-y">
|
||||
<div v-for="message in messages" class="message-item" :class="{ log: !message.sender }">
|
||||
<strong v-if="message.sender">{{ message.sender }}</strong>
|
||||
<span>{{ message.body }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer row">
|
||||
<textarea @keypress="onChatKeypress" placeholder="Send a message" tabindex="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- https://github.com/stomp-js/stomp-websocket -->
|
||||
<script src="stomp-3.1.1.min.js"></script>
|
||||
<script src="client.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,98 +1,98 @@
|
||||
/* Generic layout rules */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.row, .column {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.row {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.scroll-x {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.scroll-y {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Specific layout rules */
|
||||
|
||||
.main.column {
|
||||
left: 0;
|
||||
right: 300px;
|
||||
}
|
||||
|
||||
.chat.column {
|
||||
width: 300px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.header.row {
|
||||
height: 75px;
|
||||
line-height: 75px;
|
||||
}
|
||||
|
||||
.body.row {
|
||||
top: 75px;
|
||||
bottom: 66px;
|
||||
}
|
||||
|
||||
.body.row.nofooter {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.body.row.noheader {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.footer.row {
|
||||
height: 66px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.chat.column {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
.chat.column .body {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.chat.column .footer {
|
||||
border-top: 1px solid black;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.lobby.column {
|
||||
width: 300px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.lobby.column .body {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.lobby.column.primary {
|
||||
width: inherit;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.game.column.primary {
|
||||
left: 300px;
|
||||
right: 0;
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
/* Generic layout rules */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.row, .column {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.row {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.scroll-x {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.scroll-y {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Specific layout rules */
|
||||
|
||||
.main.column {
|
||||
left: 0;
|
||||
right: 300px;
|
||||
}
|
||||
|
||||
.chat.column {
|
||||
width: 300px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.header.row {
|
||||
height: 75px;
|
||||
line-height: 75px;
|
||||
}
|
||||
|
||||
.body.row {
|
||||
top: 75px;
|
||||
bottom: 66px;
|
||||
}
|
||||
|
||||
.body.row.nofooter {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.body.row.noheader {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.footer.row {
|
||||
height: 66px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.chat.column {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
.chat.column .body {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.chat.column .footer {
|
||||
border-top: 1px solid black;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.lobby.column {
|
||||
width: 300px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.lobby.column .body {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.lobby.column.primary {
|
||||
width: inherit;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.game.column.primary {
|
||||
left: 300px;
|
||||
right: 0;
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#skipDiv {
|
||||
margin-top: 20px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
#skipButton {
|
||||
width: 200px;
|
||||
font-size: 28px;
|
||||
font-variant: small-caps;
|
||||
background-color: black;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#skipDiv {
|
||||
margin-top: 20px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
#skipButton {
|
||||
width: 200px;
|
||||
font-size: 28px;
|
||||
font-variant: small-caps;
|
||||
background-color: black;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lingo</title>
|
||||
<link rel="stylesheet" href="practice.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="300" height="400"></canvas>
|
||||
<div id="skipDiv" class="hidden">
|
||||
<button id="skipButton" type="button">Skip Word</button>
|
||||
</div>
|
||||
|
||||
<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="practice.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lingo</title>
|
||||
<link rel="stylesheet" href="practice.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="300" height="400"></canvas>
|
||||
<div id="skipDiv" class="hidden">
|
||||
<button id="skipButton" type="button">Skip Word</button>
|
||||
</div>
|
||||
|
||||
<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="practice.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,187 +1,187 @@
|
||||
/* Common rules */
|
||||
|
||||
.centered {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: linen;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
button:hover:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Main area */
|
||||
|
||||
.header {
|
||||
font-size: 1.6em;
|
||||
color: white;
|
||||
background: steelblue;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
/* Chat pane */
|
||||
|
||||
.message-item {
|
||||
border-top: 1px solid black;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.message-item:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.chat.column textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
background-color: linen;
|
||||
border-color: transparent;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.chat.column textarea::-webkit-input-placeholder {
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
.chat.column textarea::-moz-placeholder {
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
.chat.column textarea:-ms-input-placeholder {
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
/* Lobby pane */
|
||||
|
||||
.panel {
|
||||
margin: 20px 0;
|
||||
border: 1px solid transparent;
|
||||
border-color: #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
background-color: lightgray;
|
||||
border-color: #ddd;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.panel>.list-group .list-group-item {
|
||||
font-size: 1em;
|
||||
border-width: 1px 0;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: -1px;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
button.list-group-item {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button.list-group-item:hover {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
button.list-group-item:disabled {
|
||||
color: black;
|
||||
}
|
||||
|
||||
button.list-group-item:hover:disabled {
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 120px;
|
||||
margin-left: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.create.button {
|
||||
background-color: steelblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.leave.button {
|
||||
background-color: lightcoral;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* Nickname pane */
|
||||
|
||||
.form {
|
||||
padding-top: 48px;
|
||||
text-align: center;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 300px;
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid black;
|
||||
color: steelblue;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
/* Common rules */
|
||||
|
||||
.centered {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: linen;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
button:hover:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Main area */
|
||||
|
||||
.header {
|
||||
font-size: 1.6em;
|
||||
color: white;
|
||||
background: steelblue;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
/* Chat pane */
|
||||
|
||||
.message-item {
|
||||
border-top: 1px solid black;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.message-item:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.chat.column textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
background-color: linen;
|
||||
border-color: transparent;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.chat.column textarea::-webkit-input-placeholder {
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
.chat.column textarea::-moz-placeholder {
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
.chat.column textarea:-ms-input-placeholder {
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
/* Lobby pane */
|
||||
|
||||
.panel {
|
||||
margin: 20px 0;
|
||||
border: 1px solid transparent;
|
||||
border-color: #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
background-color: lightgray;
|
||||
border-color: #ddd;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
color: steelblue;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.panel>.list-group .list-group-item {
|
||||
font-size: 1em;
|
||||
border-width: 1px 0;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: -1px;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
button.list-group-item {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button.list-group-item:hover {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
button.list-group-item:disabled {
|
||||
color: black;
|
||||
}
|
||||
|
||||
button.list-group-item:hover:disabled {
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 120px;
|
||||
margin-left: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.create.button {
|
||||
background-color: steelblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.leave.button {
|
||||
background-color: lightcoral;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* Nickname pane */
|
||||
|
||||
.form {
|
||||
padding-top: 48px;
|
||||
text-align: center;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 300px;
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid black;
|
||||
color: steelblue;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user