Add support for six letter words

This commit is contained in:
Charles Gould 2019-05-17 23:42:09 -04:00
parent a9171f5b04
commit 04af0b200e
40 changed files with 818169 additions and 148 deletions

View File

@ -5,10 +5,10 @@ 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
#web.base.url: https://lingo.charego.com
#web.socket.url: wss://lingo.charego.com/sockjs
# Logging
logging:
level:
lingo: DEBUG
com.charego.lingo: DEBUG

View File

@ -1,15 +1,12 @@
package com.charego.lingo.client.bootstrap;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import com.charego.lingo.client.multiplayer.MultiplayerConfig;
import com.charego.lingo.client.multiplayer.MultiplayerPresenter;
import com.charego.lingo.client.singleplayer.SinglePlayerPresenter;
import com.charego.lingo.client.util.FxmlController;
import com.charego.lingo.common.WordReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -35,6 +32,9 @@ public class LingoPresenter implements FxmlController {
@Autowired
private ExecutorService executorService;
@Autowired
private WordRepository wordRepo;
@FXML
private BorderPane content;
@ -88,9 +88,8 @@ public class LingoPresenter implements FxmlController {
// 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 -> {
final int wordLength = 5;
SinglePlayerPresenter presenter = new SinglePlayerPresenter(wordRepo, wordLength, e -> {
log.info("Closing single player...");
content.setCenter(gameModeChooser);
});

View File

@ -0,0 +1,44 @@
package com.charego.lingo.client.bootstrap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Component;
import com.charego.lingo.common.WordReader;
import javax.annotation.PostConstruct;
@Component
public class WordRepository {
private final Map<Integer, Set<String>> wordMap = new HashMap<>();
private final Map<Integer, List<String>> commonWordMap = new HashMap<>();
@PostConstruct
public void init() throws IOException {
final Set<String> words5 = WordReader.readFileToSet("/SOWPODS2007/words5.txt");
final Set<String> words6 = WordReader.readFileToSet("/SOWPODS2007/words6.txt");
final List<String> words5Common = WordReader.readFileToList("/SOWPODS2007/words5-common.txt");
final List<String> words6Common = WordReader.readFileToList("/SOWPODS2007/words6-common.txt");
wordMap.put(5, words5);
wordMap.put(6, words6);
commonWordMap.put(5, words5Common);
commonWordMap.put(6, words6Common);
}
public Set<String> getWords(int length) {
return Collections.unmodifiableSet(wordMap.get(length));
}
public List<String> getCommonWords(int length) {
return new ArrayList<>(commonWordMap.get(length));
}
}

View File

@ -77,6 +77,8 @@ public class MultiplayerPresenter implements FxmlController {
private GraphicsContext gc;
private Game myGame;
private String lastWord;
private PlayerBoard playerBoard;
@ -116,8 +118,6 @@ public class MultiplayerPresenter implements FxmlController {
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();
@ -154,7 +154,7 @@ public class MultiplayerPresenter implements FxmlController {
}
if (!joinedGame) {
log.debug("Hosting game...");
stompTemplate.getSession().send("/app/hostGame", null);
stompTemplate.getSession().send("/app/hostGame5", null);
}
});
}
@ -202,8 +202,12 @@ public class MultiplayerPresenter implements FxmlController {
private void repaint() {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
playerBoard.drawBoard();
opponentBoard.drawBoard();
if (playerBoard != null) {
playerBoard.drawBoard();
}
if (opponentBoard != null) {
opponentBoard.drawBoard();
}
drawLastWord();
}
@ -234,6 +238,9 @@ public class MultiplayerPresenter implements FxmlController {
private void handleMessage(Game game) {
log.debug("{} closed Game {}", game.getPlayerOne(), game.getId());
if (game.getPlayerOne().getUsername().equals(username)) {
myGame = null;
}
}
}
@ -251,6 +258,11 @@ public class MultiplayerPresenter implements FxmlController {
private void handleMessage(Game game) {
log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId());
if (game.getPlayerOne().getUsername().equals(username)) {
myGame = game;
playerBoard = new PlayerBoard(canvas, 50, 50, game.getWordLength());
opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50, game.getWordLength());
}
}
}
@ -268,6 +280,11 @@ public class MultiplayerPresenter implements FxmlController {
private void handleMessage(Game game) {
log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne());
if (game.getPlayerTwo().getUsername().equals(username)) {
myGame = game;
playerBoard = new PlayerBoard(canvas, 50, 50, game.getWordLength());
opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50, game.getWordLength());
}
}
}
@ -289,6 +306,9 @@ public class MultiplayerPresenter implements FxmlController {
log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne());
if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) {
Platform.runLater(() -> {
if (gameLeaver.equals(username)) {
myGame = null;
}
clearBoards(true);
showWaitingAnimation(true);
opponentUsername = null;
@ -404,11 +424,11 @@ public class MultiplayerPresenter implements FxmlController {
final int[] result = report.getResult();
log.info("My result: " + Arrays.toString(result));
Platform.runLater(() -> {
if (Game.isInvalid(result)) {
playerBoard.addGuess("-----");
if (myGame.isInvalid(result)) {
playerBoard.addGuess("-".repeat(myGame.getWordLength()));
} else {
for (int i = 0; i < Game.WORD_LENGTH; i++) {
if (result[i] == Game.CORRECT_CHARACTER) {
for (int i = 0; i < myGame.getWordLength(); i++) {
if (result[i] == Game.CORRECT_CHARACTER_POSITION) {
playerBoard.setProgress(i, guess.charAt(i));
}
}

View File

@ -1,9 +1,8 @@
package com.charego.lingo.client.singleplayer;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import com.charego.lingo.client.bootstrap.WordRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,7 +42,7 @@ public class SinglePlayerPresenter {
private final Game game;
public SinglePlayerPresenter(List<String> words, Set<String> guesses, EventHandler<ActionEvent> backButtonHandler) {
public SinglePlayerPresenter(WordRepository wordRepo, int wordLength, EventHandler<ActionEvent> backButtonHandler) {
backButton = new Button("Back");
backButton.getStyleClass().add("game-nav");
StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT);
@ -63,10 +62,10 @@ public class SinglePlayerPresenter {
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);
gameBoard = new PlayerBoard(canvas, 200, 60, wordLength);
game = new Game(new Player("solo"), wordLength);
game.setAcceptableGuesses(wordRepo.getWords(wordLength));
game.setPossibleWords(wordRepo.getCommonWords(wordLength));
}
private void clearBoards(boolean clearScore) {
@ -93,7 +92,7 @@ public class SinglePlayerPresenter {
Report report = new Report();
report.setGuess(guess);
report.setResult(result);
if (Game.isCorrect(result)) {
if (game.isCorrect(result)) {
final String newWord = game.newWord();
final String firstLetter = String.valueOf(newWord.charAt(0));
report.setCorrect(true);
@ -131,11 +130,11 @@ public class SinglePlayerPresenter {
final int[] result = report.getResult();
log.info("My result: " + Arrays.toString(result));
Platform.runLater(() -> {
if (Game.isInvalid(result)) {
gameBoard.addGuess("-----");
if (game.isInvalid(result)) {
gameBoard.addGuess("-".repeat(game.getWordLength()));
} else {
for (int i = 0; i < Game.WORD_LENGTH; i++) {
if (result[i] == Game.CORRECT_CHARACTER) {
for (int i = 0; i < game.getWordLength(); i++) {
if (result[i] == Game.CORRECT_CHARACTER_POSITION) {
gameBoard.setProgress(i, guess.charAt(i));
}
}

View File

@ -7,7 +7,9 @@ public abstract class Board {
public static final double HEIGHT = 300;
public static final double WIDTH = 250;
public static final double SIDE = 50;
/** The height of a character box */
public static final double CHARACTER_HEIGHT = 50;
protected final Canvas canvas;
@ -19,11 +21,19 @@ public abstract class Board {
/** The topmost y-coordinate */
protected final double yInit;
public Board(Canvas canvas, double xInit, double yInit) {
/** The width of a character box */
protected final double characterWidth;
/** The length of a word in this game */
protected final int wordLength;
public Board(Canvas canvas, double xInit, double yInit, int wordLength) {
this.canvas = canvas;
this.gc = canvas.getGraphicsContext2D();
this.xInit = xInit;
this.yInit = yInit;
this.wordLength = wordLength;
this.characterWidth = WIDTH / wordLength;
}
public void clearBoard() {

View File

@ -14,8 +14,8 @@ public abstract class GameBoard extends Board {
/** Tracks the player's score */
protected int score;
public GameBoard(Canvas canvas, double xInit, double yInit) {
super(canvas, xInit, yInit);
public GameBoard(Canvas canvas, double xInit, double yInit, int wordLength) {
super(canvas, xInit, yInit, wordLength);
}
@Override
@ -33,11 +33,11 @@ public abstract class GameBoard extends Board {
protected void drawGrid() {
gc.beginPath();
for (int x = 0; x <= WIDTH; x += SIDE) {
for (double x = 0; x <= WIDTH; x += characterWidth) {
gc.moveTo(xInit + x, yInit);
gc.lineTo(xInit + x, yInit + HEIGHT);
}
for (int y = 0; y <= HEIGHT; y += SIDE) {
for (double y = 0; y <= HEIGHT; y += CHARACTER_HEIGHT) {
gc.moveTo(xInit, yInit + y);
gc.lineTo(xInit + WIDTH, yInit + y);
}
@ -46,22 +46,22 @@ public abstract class GameBoard extends Board {
}
protected void drawResults() {
double y = yInit + SIDE * 1.5;
double y = yInit + CHARACTER_HEIGHT * 1.5;
int numResults = Math.min(4, results.size());
for (int i = 0; i < numResults; i++) {
double x = xInit + SIDE * 0.5;
double x = xInit + characterWidth * 0.5;
int[] result = results.get(results.size() - numResults + i);
for (int j = 0; j < 5; j++) {
for (int j = 0; j < wordLength; j++) {
if (result[j] == 1) {
gc.setFill(Color.YELLOW);
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
gc.fillRect(x - characterWidth * 0.5, y - CHARACTER_HEIGHT * 0.5, characterWidth, CHARACTER_HEIGHT);
} else if (result[j] == 2) {
gc.setFill(Color.ORANGE);
gc.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
gc.fillRect(x - characterWidth * 0.5, y - CHARACTER_HEIGHT * 0.5, characterWidth, CHARACTER_HEIGHT);
}
x += SIDE;
x += characterWidth;
}
y += SIDE;
y += CHARACTER_HEIGHT;
}
}

View File

@ -4,8 +4,8 @@ import javafx.scene.canvas.Canvas;
public class OpponentBoard extends GameBoard {
public OpponentBoard(Canvas canvas, double xInit, double yInit) {
super(canvas, xInit, yInit);
public OpponentBoard(Canvas canvas, double xInit, double yInit, int wordLength) {
super(canvas, xInit, yInit, wordLength);
}
@Override

View File

@ -19,8 +19,8 @@ public class PlayerBoard extends GameBoard {
/** 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);
public PlayerBoard(Canvas canvas, double xInit, double yInit, int wordLength) {
super(canvas, xInit, yInit, wordLength);
}
@Override
@ -44,39 +44,39 @@ public class PlayerBoard extends GameBoard {
private void drawInput() {
gc.setFill(Color.GREEN);
double x = xInit + SIDE * 0.5;
double y = yInit + SIDE * 0.5;
double x = xInit + characterWidth * 0.5;
double y = yInit + CHARACTER_HEIGHT * 0.5;
for (int i = 0; i < guess.length(); i++) {
String character = String.valueOf(guess.charAt(i));
gc.fillText(character, x, y);
x += SIDE;
x += characterWidth;
}
}
private double drawGuesses() {
double y = yInit + SIDE * 1.5;
double y = yInit + CHARACTER_HEIGHT * 1.5;
double numGuesses = Math.min(4, guesses.size());
for (int i = 0; i < numGuesses; i++) {
double x = xInit + SIDE * 0.5;
double x = xInit + characterWidth * 0.5;
String guess = guesses.get((int) (guesses.size() - numGuesses + i));
for (int j = 0; j < 5; j++) {
for (int j = 0; j < wordLength; j++) {
String character = String.valueOf(guess.charAt(j));
gc.setFill(Color.GREEN);
gc.fillText(character, x, y);
x += SIDE;
x += characterWidth;
}
y += SIDE;
y += CHARACTER_HEIGHT;
}
return y;
}
private void drawHint(double yStart) {
double x = xInit + SIDE * 0.5;
for (int i = 0; i < 5; i++) {
double x = xInit + characterWidth * 0.5;
for (int i = 0; i < wordLength; i++) {
if (progress.containsKey(i)) {
gc.fillText(progress.get(i), x, yStart);
}
x += SIDE;
x += characterWidth;
}
}
@ -97,7 +97,7 @@ public class PlayerBoard extends GameBoard {
}
public String handleEnter() {
if (guess.length() == 5) {
if (guess.length() == wordLength) {
final String value = guess.toString();
guess.setLength(0);
return value;
@ -106,7 +106,7 @@ public class PlayerBoard extends GameBoard {
}
public boolean handleLetter(String letter) {
if (guess.length() < 5) {
if (guess.length() < wordLength) {
guess.append(letter);
return true;
}

View File

@ -10,11 +10,7 @@ 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 };
public static final int CORRECT_CHARACTER_POSITION = 2;
private static final AtomicInteger idCounter = new AtomicInteger(0);
@ -32,17 +28,22 @@ public class Game {
private int wordIndex = 0;
private int wordLength;
private int[] invalidGuess;
public Game() {
// Empty constructor required for serialization
}
public Game(Player host) {
public Game(Player host, int wordLength) {
this.id = idCounter.incrementAndGet();
this.playerOne = host;
setWordLength(wordLength);
}
private static int indexOf(char[] array, char searchTerm) {
for (int i = 0; i < WORD_LENGTH; i++) {
private int indexOf(char[] array, char searchTerm) {
for (int i = 0; i < wordLength; i++) {
if (array[i] == searchTerm) {
return i;
}
@ -50,36 +51,36 @@ public class Game {
return -1;
}
public static boolean isCorrect(int[] result) {
for (int i = 0; i < WORD_LENGTH; i++) {
if (result[i] != CORRECT_CHARACTER) {
public boolean isCorrect(int[] result) {
for (int i = 0; i < wordLength; i++) {
if (result[i] != CORRECT_CHARACTER_POSITION) {
return false;
}
}
return true;
}
public static boolean isInvalid(int[] result) {
return Arrays.equals(result, INVALID_GUESS);
public boolean isInvalid(int[] result) {
return Arrays.equals(result, invalidGuess);
}
public int[] evaluate(String guess) {
if (!acceptableGuesses.contains(guess)) {
return INVALID_GUESS;
return invalidGuess;
}
// the guess is acceptable
int[] result = new int[WORD_LENGTH];
char[] remaining = new char[WORD_LENGTH];
for (int i = 0; i < WORD_LENGTH; i++) {
int[] result = new int[wordLength];
char[] remaining = new char[wordLength];
for (int i = 0; i < wordLength; i++) {
if (guess.charAt(i) == word.charAt(i)) {
result[i] = CORRECT_CHARACTER;
result[i] = CORRECT_CHARACTER_POSITION;
} else {
result[i] = INCORRECT_CHARACTER;
remaining[i] = word.charAt(i);
}
}
for (int i = 0; i < WORD_LENGTH; i++) {
for (int i = 0; i < wordLength; i++) {
if (result[i] == INCORRECT_CHARACTER) {
int index = indexOf(remaining, guess.charAt(i));
if (index != -1) {
@ -103,6 +104,10 @@ public class Game {
return playerTwo;
}
public int getWordLength() {
return wordLength;
}
public String newGame() {
Collections.shuffle(possibleWords);
wordIndex = 0;
@ -134,4 +139,13 @@ public class Game {
this.possibleWords = value;
}
public void setWordLength(int value) {
this.wordLength = value;
this.invalidGuess = new int[value];
for (int i = 0; i < value; i++) {
/* Nein nein nein nein nein! */
this.invalidGuess[i] = 9;
}
}
}

View File

@ -18,7 +18,7 @@ public class WordReader {
final BufferedReader bufferedReader = new BufferedReader(streamReader)) {
String line = null;
while ((line = bufferedReader.readLine()) != null) {
c.add(line);
c.add(line.toUpperCase());
}
}
}

View File

@ -0,0 +1,6 @@
Downloaded from Donald Knuth's [website](https://www-cs-faculty.stanford.edu/~knuth/programs.html):
> Datasets for exercises in The Art of Computer Programming:
>
> [wordlists.tgz](https://www-cs-faculty.stanford.edu/~knuth/wordlists.tgz)
> - English words of lengths 2 thru 12 [OSPD4], ordered by frequency

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
OF
TO
IN
IS
IT
ON
BE
AS
AT
BY
HE
OR
AN
WE
UP
SO
IF
DO
NO
ME
MY
GO
ER
US
AM
OH
DE
MM
ET
AL
LA
AH
HA
EH
AD
YA
EN
RE
YE
EX
YO
MA
NA
TA
ED
SH
HO
UM
EM
UN
LO
WO
HI
LI
TI
NE
BA
PA
SI
ID
MI
MO
FE
EL
OX
AB
AR
ES
OW
PE
BO
OI
OP
FA
UH
BI
PI
MU
HM
AY
XI
KI
NU
OS
AA
AI
AG
AE
UT
AW
OY
KA
AX
OD
QI
OM
JO
ZA
EF
OE
XU

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
Downloaded from [Word Game Dictionary](https://www.wordgamedictionary.com/word-lists/):
> All of our word lists formerly part of the TWL06 and OWL3 have been updated to the new (OTCWL2014). Included are all our words in the scrabble dictionary results and scrabble word finder results.
>
> Words of a particular length:
> - ...
> - 5 Letter Words ([json](https://www.wordgamedictionary.com/word-lists/5-letter-words/5-letter-words.json))
> - 6 Letter Words ([json](https://www.wordgamedictionary.com/word-lists/6-letter-words/6-letter-words.json))
> - ...

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
Downloaded from John Chew's [website](http://www.poslarchive.com/math/scrabble/lists/).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
Downloaded from [Natural Language Corpus Data: Beautiful Data](https://norvig.com/ngrams/).
> Files for Download
>
> | Size | Filename | Description |
> | ---- | -------- | ----------- |
> | 4.9 MB | [count_1w.txt](https://norvig.com/ngrams/count_1w.txt) | The 1/3 million most frequent words, all lowercase, with counts. |
> | 1.5 MB | [count_1w100k.txt](https://norvig.com/ngrams/count_1w100k.txt) | A word count file with 100,000 most popular words, all uppercase. |
> | 1.9 MB | [TWL06.txt](https://norvig.com/ngrams/TWL06.txt) | The [Tournament Word List](http://en.wikipedia.org/wiki/Official_Tournament_and_Club_Word_List) (178,690 words) -- used by North American Scrabble players. |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -85,7 +85,7 @@ public class LingoController {
final Report playerReport = new Report();
final Report opponentReport = new Report();
playerReport.setGuess(guess);
if (Game.isCorrect(result)) {
if (game.isCorrect(result)) {
final String newWord = game.newWord();
final String firstLetter = String.valueOf(newWord.charAt(0));
log.info("New word: {}", newWord);
@ -108,8 +108,17 @@ public class LingoController {
sendToPlayer(opponent, Destinations.OPPONENT_REPORTS, opponentReport);
}
@MessageMapping("/hostGame")
public synchronized void hostGame(@Header(SESSION_ID_HEADER) String sessionId) {
@MessageMapping("/hostGame5")
public synchronized void hostGame5(@Header(SESSION_ID_HEADER) String sessionId) {
hostGame(sessionId, 5);
}
@MessageMapping("/hostGame6")
public synchronized void hostGame6(@Header(SESSION_ID_HEADER) String sessionId) {
hostGame(sessionId, 6);
}
private synchronized void hostGame(String sessionId, int wordLength) {
final Player player = sessionManager.getPlayer(sessionId);
final String username = player.getUsername();
if (username == null) {
@ -120,7 +129,9 @@ public class LingoController {
log.warn("{} is in a game already", player);
return;
}
final Game game = new Game(player);
final Game game = new Game(player, wordLength);
game.setAcceptableGuesses(wordRepo.getWords(wordLength));
game.setPossibleWords(wordRepo.getCommonWords(wordLength));
gameById.put(game.getId(), game);
gameByPlayer.put(player, game);
log.info("{} hosted Game {}", player, game.getId());
@ -152,9 +163,6 @@ public class LingoController {
// Start the game immediately
// TODO: require the players to "ready up"
game.setAcceptableGuesses(wordRepo.getGuesses());
game.setPossibleWords(wordRepo.getWords());
final String firstWord = game.newGame();
final String firstLetter = String.valueOf(firstWord.charAt(0));
log.info("First word: {}", firstWord);

View File

@ -41,9 +41,10 @@ public class PracticeController {
public void practiceGame(@Header(SESSION_ID_HEADER) String sessionId) {
final Player player = sessionManager.getPlayer(sessionId);
log.info("{} is practicing", sessionId);
final Game game = new Game(player);
game.setAcceptableGuesses(wordRepo.getGuesses());
game.setPossibleWords(wordRepo.getWords());
final int wordLength = 5;
final Game game = new Game(player, wordLength);
game.setAcceptableGuesses(wordRepo.getWords(wordLength));
game.setPossibleWords(wordRepo.getCommonWords(wordLength));
practiceByPlayer.put(player, game);
final String firstWord = game.newGame();
final String firstLetter = String.valueOf(firstWord.charAt(0));
@ -62,7 +63,7 @@ public class PracticeController {
// Generate report
final Report report = new Report();
report.setGuess(guess);
if (Game.isCorrect(result)) {
if (game.isCorrect(result)) {
final String newWord = game.newWord();
final String firstLetter = String.valueOf(newWord.charAt(0));
log.info("New word: {}", newWord);

View File

@ -3,37 +3,42 @@ package com.charego.lingo.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Component;
import com.charego.lingo.common.WordReader;
import javax.annotation.PostConstruct;
@Component
public class WordRepository {
private final Set<String> guesses;
private final Map<Integer, Set<String>> wordMap = new HashMap<>();
private final Map<Integer, List<String>> commonWordMap = new HashMap<>();
private final List<String> words;
@PostConstruct
public void init() throws IOException {
final Set<String> words5 = WordReader.readFileToSet("/SOWPODS2007/words5.txt");
final Set<String> words6 = WordReader.readFileToSet("/SOWPODS2007/words6.txt");
final List<String> words5Common = WordReader.readFileToList("/SOWPODS2007/words5-common.txt");
final List<String> words6Common = WordReader.readFileToList("/SOWPODS2007/words6-common.txt");
public WordRepository() throws IOException {
guesses = WordReader.readFileToSet("/guesses.txt");
words = WordReader.readFileToList("/words.txt");
wordMap.put(5, words5);
wordMap.put(6, words6);
commonWordMap.put(5, words5Common);
commonWordMap.put(6, words6Common);
}
/**
* Returns the set of acceptable guesses (unmodifiable).
*/
public Set<String> getGuesses() {
return Collections.unmodifiableSet(guesses);
public Set<String> getWords(int length) {
return Collections.unmodifiableSet(wordMap.get(length));
}
/**
* Returns a copy of the list of potential answers (OK to shuffle it).
*/
public List<String> getWords() {
return new ArrayList<>(words);
public List<String> getCommonWords(int length) {
return new ArrayList<>(commonWordMap.get(length));
}
}

View File

@ -3,7 +3,7 @@ var KEYCODE_RETURN = 13;
var HEIGHT = 300;
var WIDTH = 250;
var SIDE = 50;
var CHARACTER_HEIGHT = 50;
var MARGIN_TOP = 100;
var MARGIN_BOTTOM = 75;
@ -14,10 +14,10 @@ var vm = new Vue({
el: '#vue-app',
data: {
games: [],
gameId: null,
messages: [],
username: null,
usernameError: '',
myGame: null,
myScore: 0,
myGuess: '',
myGuesses: [],
@ -30,11 +30,16 @@ var vm = new Vue({
},
computed: {
inGame: function() {
return this.gameId !== null;
return this.myGame !== null;
},
inStartedGame: function() {
var game = this.getGame(this.gameId);
return game !== null && game.started === true;
return this.myGame !== null && this.myGame.started === true;
},
characterWidth: function() {
return this.myGame === null ? 50 : (WIDTH / this.myGame.wordLength);
},
wordLength: function() {
return this.myGame === null ? 5 : this.myGame.wordLength;
}
},
directives: {
@ -89,11 +94,11 @@ var vm = new Vue({
},
drawGrid: function(ctx, xOrigin, yOrigin) {
ctx.beginPath();
for (var x = 0; x <= WIDTH; x += SIDE) {
for (var x = 0; x <= WIDTH; x += this.characterWidth) {
ctx.moveTo(xOrigin + x, yOrigin);
ctx.lineTo(xOrigin + x, yOrigin + HEIGHT);
}
for (var y = 0; y <= HEIGHT; y += SIDE) {
for (var y = 0; y <= HEIGHT; y += CHARACTER_HEIGHT) {
ctx.moveTo(xOrigin, yOrigin + y);
ctx.lineTo(xOrigin + WIDTH, yOrigin + y);
}
@ -102,61 +107,61 @@ var vm = new Vue({
},
drawInput: function(ctx, xOrigin, yOrigin, input) {
ctx.fillStyle = 'green';
var x = xOrigin + SIDE * 0.5;
var y = yOrigin + SIDE * 0.5;
var x = xOrigin + this.characterWidth * 0.5;
var y = yOrigin + CHARACTER_HEIGHT * 0.5;
for (var i = 0; i < input.length; i++) {
ctx.fillText(input[i], x, y);
x += SIDE;
x += this.characterWidth;
}
},
drawGuesses: function(ctx, xOrigin, yOrigin, guesses, results) {
var y = yOrigin + SIDE * 1.5;
var y = yOrigin + CHARACTER_HEIGHT * 1.5;
var numGuesses = Math.min(4, guesses.length);
for (var i = 0; i < numGuesses; i++) {
var x = xOrigin + SIDE * 0.5;
var x = xOrigin + this.characterWidth * 0.5;
var guess = guesses[guesses.length - numGuesses + i];
var result = results[results.length - numGuesses + i];
for (var j = 0; j < 5; j++) {
for (var j = 0; j < this.wordLength; j++) {
if (result[j] === 1) {
ctx.fillStyle = 'yellow';
ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
ctx.fillRect(x - this.characterWidth * 0.5, y - CHARACTER_HEIGHT * 0.5, this.characterWidth, CHARACTER_HEIGHT);
} else if (result[j] === 2) {
ctx.fillStyle = 'orange';
ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
ctx.fillRect(x - this.characterWidth * 0.5, y - CHARACTER_HEIGHT * 0.5, this.characterWidth, CHARACTER_HEIGHT);
}
ctx.fillStyle = 'green';
ctx.fillText(guess[j], x, y);
x += SIDE;
x += this.characterWidth;
}
y += SIDE;
y += CHARACTER_HEIGHT;
}
return y;
},
drawResults: function(ctx, xOrigin, yOrigin, results) {
var y = yOrigin + SIDE * 1.5;
var y = yOrigin + CHARACTER_HEIGHT * 1.5;
var numResults = Math.min(4, results.length);
for (var i = 0; i < numResults; i++) {
var x = xOrigin + SIDE * 0.5;
var x = xOrigin + this.characterWidth * 0.5;
var result = results[results.length - numResults + i];
for (var j = 0; j < 5; j++) {
for (var j = 0; j < this.wordLength; j++) {
if (result[j] === 1) {
ctx.fillStyle = 'yellow';
ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
ctx.fillRect(x - this.characterWidth * 0.5, y - CHARACTER_HEIGHT * 0.5, this.characterWidth, CHARACTER_HEIGHT);
} else if (result[j] === 2) {
ctx.fillStyle = 'orange';
ctx.fillRect(x - SIDE * 0.5, y - SIDE * 0.5, SIDE, SIDE);
ctx.fillRect(x - this.characterWidth * 0.5, y - CHARACTER_HEIGHT * 0.5, this.characterWidth, CHARACTER_HEIGHT);
}
x += SIDE;
x += this.characterWidth;
}
y += SIDE;
y += CHARACTER_HEIGHT;
}
return y;
},
drawHint: function(ctx, xOrigin, yOrigin, progress) {
var x = xOrigin + SIDE * 0.5;
for (var i = 0; i < 5; i++) {
var x = xOrigin + this.characterWidth * 0.5;
for (var i = 0; i < this.wordLength; i++) {
ctx.fillText(progress[i], x, yOrigin);
x += SIDE;
x += this.characterWidth;
}
},
getGame: function(gameId) {
@ -167,8 +172,11 @@ var vm = new Vue({
}
return null;
},
hostGame: function(e) {
client.publish({destination: '/app/hostGame'})
hostGame5: function(e) {
client.publish({destination: '/app/hostGame5'})
},
hostGame6: function(e) {
client.publish({destination: '/app/hostGame6'})
},
joinGame: function(e) {
// Discard 'game-' prefix
@ -196,7 +204,7 @@ var vm = new Vue({
this.repaint();
}
else if (e.which === KEYCODE_RETURN) {
if (this.myGuess.length === 5) {
if (this.myGuess.length === this.wordLength) {
client.publish({destination: '/app/guess', body: this.myGuess})
this.myGuess = '';
this.repaint();
@ -210,7 +218,7 @@ var vm = new Vue({
charCode = charCode - 32;
}
var char = String.fromCharCode(charCode);
if (this.myGuess.length < 5) {
if (this.myGuess.length < this.wordLength) {
this.myGuess += char;
this.repaint();
}
@ -249,7 +257,10 @@ var vm = new Vue({
}
this.myGuess = '';
this.myGuesses = [];
this.myProgress = [firstLetter, '', '', '', ''];
this.myProgress = [firstLetter];
for (var i = 1; i < this.wordLength; i++) {
this.myProgress[i] = '';
}
this.myResults = [];
this.opponentResults = [];
if (clearScore) {
@ -394,6 +405,7 @@ function start() {
id: game.id,
playerOne: game.playerOne.username,
playerTwo: game.playerTwo ? game.playerTwo.username : null,
wordLength: game.wordLength,
started: game.playerTwo !== null
});
}
@ -469,7 +481,7 @@ function onGameClosed(message) {
var playerOne = game.playerOne.username;
console.log(playerOne + ' closed Game ' + gameId);
if (playerOne === vm.username) {
vm.gameId = null;
vm.myGame = null;
}
vm.removeGame(gameId);
}
@ -477,21 +489,25 @@ function onGameClosed(message) {
function onGameHosted(message) {
var game = JSON.parse(message.body);
var gameId = game.id;
var wordLength = game.wordLength;
var playerOne = game.playerOne.username;
console.log(playerOne + ' hosted Game ' + gameId);
vm.games.push({
var vueGame = {
id: gameId,
playerOne: playerOne,
wordLength: wordLength,
started: false
});
};
vm.games.push(vueGame);
if (playerOne === vm.username) {
vm.gameId = gameId;
vm.myGame = vueGame;
}
}
function onGameJoined(message) {
var game = JSON.parse(message.body);
var gameId = game.id;
var wordLength = game.wordLength;
var playerOne = game.playerOne.username;
var playerTwo = game.playerTwo.username;
@ -499,16 +515,19 @@ function onGameJoined(message) {
console.log(message);
addChatAnnouncement(message);
var vueGame = null;
for (var i = 0; i < vm.games.length; i++) {
if (vm.games[i].id === gameId) {
vm.games[i].playerTwo = playerTwo;
vm.games[i].wordLength = wordLength;
vm.games[i].started = true;
vueGame = vm.games[i];
break;
}
}
if (playerTwo === vm.username) {
vm.gameId = gameId;
vm.myGame = vueGame;
}
}
@ -531,7 +550,7 @@ function onGameLeft(message) {
}
}
if (gameLeaver === vm.username) {
vm.gameId = null;
vm.myGame = null;
}
if (previousPlayers.indexOf(vm.username) != -1) {
vm.opponentUsername = null;
@ -580,9 +599,10 @@ function onPlayerReport(message) {
var guess = report.guess;
var result = report.result;
if (result[0] === 9) {
vm.myGuesses.push('-----');
var invalidGuess = '-'.repeat(vm.wordLength);
vm.myGuesses.push(invalidGuess);
} else {
for (var i = 0; i < 5; i++) {
for (var i = 0; i < vm.wordLength; i++) {
if (result[i] === 2) {
vm.myProgress[i] = guess[i];
}

View File

@ -32,13 +32,14 @@
<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
<strong>{{ game.playerOne }}</strong> wants to play a {{ game.wordLength }}-letter game
</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>
<button v-show="!inGame" @click="hostGame5" type="button" class="create button">Create 5-letter game</button>
<button v-show="!inGame" @click="hostGame6" type="button" class="create button">Create 6-letter game</button>
</div>
</div>
<div v-bind:class="{ primary: inStartedGame }" class="game column">