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 web.socket.url: ws://localhost:8080/sockjs
# Production # Production
#web.base.url: http://lingo.charego.com #web.base.url: https://lingo.charego.com
#web.socket.url: ws://lingo.charego.com/sockjs #web.socket.url: wss://lingo.charego.com/sockjs
# Logging # Logging
logging: logging:
level: level:
lingo: DEBUG com.charego.lingo: DEBUG

View File

@ -1,15 +1,12 @@
package com.charego.lingo.client.bootstrap; package com.charego.lingo.client.bootstrap;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import com.charego.lingo.client.multiplayer.MultiplayerConfig; import com.charego.lingo.client.multiplayer.MultiplayerConfig;
import com.charego.lingo.client.multiplayer.MultiplayerPresenter; import com.charego.lingo.client.multiplayer.MultiplayerPresenter;
import com.charego.lingo.client.singleplayer.SinglePlayerPresenter; import com.charego.lingo.client.singleplayer.SinglePlayerPresenter;
import com.charego.lingo.client.util.FxmlController; import com.charego.lingo.client.util.FxmlController;
import com.charego.lingo.common.WordReader;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -35,6 +32,9 @@ public class LingoPresenter implements FxmlController {
@Autowired @Autowired
private ExecutorService executorService; private ExecutorService executorService;
@Autowired
private WordRepository wordRepo;
@FXML @FXML
private BorderPane content; private BorderPane content;
@ -88,9 +88,8 @@ public class LingoPresenter implements FxmlController {
// TODO: Is there a memory leak here? // TODO: Is there a memory leak here?
try { try {
Set<String> guesses = WordReader.readFileToSet("/guesses.txt"); final int wordLength = 5;
List<String> words = WordReader.readFileToList("/words.txt"); SinglePlayerPresenter presenter = new SinglePlayerPresenter(wordRepo, wordLength, e -> {
SinglePlayerPresenter presenter = new SinglePlayerPresenter(words, guesses, e -> {
log.info("Closing single player..."); log.info("Closing single player...");
content.setCenter(gameModeChooser); 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 GraphicsContext gc;
private Game myGame;
private String lastWord; private String lastWord;
private PlayerBoard playerBoard; private PlayerBoard playerBoard;
@ -116,8 +118,6 @@ public class MultiplayerPresenter implements FxmlController {
gc.setFont(Font.font(24)); gc.setFont(Font.font(24));
gc.setTextAlign(TextAlignment.CENTER); gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER); gc.setTextBaseline(VPos.CENTER);
playerBoard = new PlayerBoard(canvas, 50, 50);
opponentBoard = new OpponentBoard(canvas, 50 + Board.WIDTH + 50, 50);
Platform.runLater(() -> { Platform.runLater(() -> {
String html = getClass().getResource("/cube-grid.html").toExternalForm(); String html = getClass().getResource("/cube-grid.html").toExternalForm();
@ -154,7 +154,7 @@ public class MultiplayerPresenter implements FxmlController {
} }
if (!joinedGame) { if (!joinedGame) {
log.debug("Hosting game..."); 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() { private void repaint() {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
playerBoard.drawBoard(); if (playerBoard != null) {
opponentBoard.drawBoard(); playerBoard.drawBoard();
}
if (opponentBoard != null) {
opponentBoard.drawBoard();
}
drawLastWord(); drawLastWord();
} }
@ -234,6 +238,9 @@ public class MultiplayerPresenter implements FxmlController {
private void handleMessage(Game game) { private void handleMessage(Game game) {
log.debug("{} closed Game {}", game.getPlayerOne(), game.getId()); log.debug("{} closed Game {}", game.getPlayerOne(), game.getId());
if (game.getPlayerOne().getUsername().equals(username)) {
myGame = null;
}
} }
} }
@ -251,6 +258,11 @@ public class MultiplayerPresenter implements FxmlController {
private void handleMessage(Game game) { private void handleMessage(Game game) {
log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId()); log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId());
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) { private void handleMessage(Game game) {
log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne()); 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()); log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne());
if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) { if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) {
Platform.runLater(() -> { Platform.runLater(() -> {
if (gameLeaver.equals(username)) {
myGame = null;
}
clearBoards(true); clearBoards(true);
showWaitingAnimation(true); showWaitingAnimation(true);
opponentUsername = null; opponentUsername = null;
@ -404,11 +424,11 @@ public class MultiplayerPresenter implements FxmlController {
final int[] result = report.getResult(); final int[] result = report.getResult();
log.info("My result: " + Arrays.toString(result)); log.info("My result: " + Arrays.toString(result));
Platform.runLater(() -> { Platform.runLater(() -> {
if (Game.isInvalid(result)) { if (myGame.isInvalid(result)) {
playerBoard.addGuess("-----"); playerBoard.addGuess("-".repeat(myGame.getWordLength()));
} else { } else {
for (int i = 0; i < Game.WORD_LENGTH; i++) { for (int i = 0; i < myGame.getWordLength(); i++) {
if (result[i] == Game.CORRECT_CHARACTER) { if (result[i] == Game.CORRECT_CHARACTER_POSITION) {
playerBoard.setProgress(i, guess.charAt(i)); playerBoard.setProgress(i, guess.charAt(i));
} }
} }

View File

@ -1,9 +1,8 @@
package com.charego.lingo.client.singleplayer; package com.charego.lingo.client.singleplayer;
import java.util.Arrays; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -43,7 +42,7 @@ public class SinglePlayerPresenter {
private final Game game; 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 = new Button("Back");
backButton.getStyleClass().add("game-nav"); backButton.getStyleClass().add("game-nav");
StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT); StackPane.setAlignment(backButton, Pos.BOTTOM_LEFT);
@ -63,10 +62,10 @@ public class SinglePlayerPresenter {
contentPane.getChildren().add(canvas); contentPane.getChildren().add(canvas);
contentPane.getChildren().add(backButton); contentPane.getChildren().add(backButton);
gameBoard = new PlayerBoard(canvas, 200, 60); gameBoard = new PlayerBoard(canvas, 200, 60, wordLength);
game = new Game(new Player("solo")); game = new Game(new Player("solo"), wordLength);
game.setAcceptableGuesses(guesses); game.setAcceptableGuesses(wordRepo.getWords(wordLength));
game.setPossibleWords(words); game.setPossibleWords(wordRepo.getCommonWords(wordLength));
} }
private void clearBoards(boolean clearScore) { private void clearBoards(boolean clearScore) {
@ -93,7 +92,7 @@ public class SinglePlayerPresenter {
Report report = new Report(); Report report = new Report();
report.setGuess(guess); report.setGuess(guess);
report.setResult(result); report.setResult(result);
if (Game.isCorrect(result)) { if (game.isCorrect(result)) {
final String newWord = game.newWord(); final String newWord = game.newWord();
final String firstLetter = String.valueOf(newWord.charAt(0)); final String firstLetter = String.valueOf(newWord.charAt(0));
report.setCorrect(true); report.setCorrect(true);
@ -131,11 +130,11 @@ public class SinglePlayerPresenter {
final int[] result = report.getResult(); final int[] result = report.getResult();
log.info("My result: " + Arrays.toString(result)); log.info("My result: " + Arrays.toString(result));
Platform.runLater(() -> { Platform.runLater(() -> {
if (Game.isInvalid(result)) { if (game.isInvalid(result)) {
gameBoard.addGuess("-----"); gameBoard.addGuess("-".repeat(game.getWordLength()));
} else { } else {
for (int i = 0; i < Game.WORD_LENGTH; i++) { for (int i = 0; i < game.getWordLength(); i++) {
if (result[i] == Game.CORRECT_CHARACTER) { if (result[i] == Game.CORRECT_CHARACTER_POSITION) {
gameBoard.setProgress(i, guess.charAt(i)); 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 HEIGHT = 300;
public static final double WIDTH = 250; 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; protected final Canvas canvas;
@ -19,11 +21,19 @@ public abstract class Board {
/** The topmost y-coordinate */ /** The topmost y-coordinate */
protected final double yInit; 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.canvas = canvas;
this.gc = canvas.getGraphicsContext2D(); this.gc = canvas.getGraphicsContext2D();
this.xInit = xInit; this.xInit = xInit;
this.yInit = yInit; this.yInit = yInit;
this.wordLength = wordLength;
this.characterWidth = WIDTH / wordLength;
} }
public void clearBoard() { public void clearBoard() {

View File

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

View File

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

View File

@ -10,11 +10,7 @@ public class Game {
public static final int INCORRECT_CHARACTER = 0; public static final int INCORRECT_CHARACTER = 0;
public static final int INCORRECT_POSITION = 1; public static final int INCORRECT_POSITION = 1;
public static final int CORRECT_CHARACTER = 2; public static final int CORRECT_CHARACTER_POSITION = 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 static final AtomicInteger idCounter = new AtomicInteger(0);
@ -32,17 +28,22 @@ public class Game {
private int wordIndex = 0; private int wordIndex = 0;
private int wordLength;
private int[] invalidGuess;
public Game() { public Game() {
// Empty constructor required for serialization // Empty constructor required for serialization
} }
public Game(Player host) { public Game(Player host, int wordLength) {
this.id = idCounter.incrementAndGet(); this.id = idCounter.incrementAndGet();
this.playerOne = host; this.playerOne = host;
setWordLength(wordLength);
} }
private static int indexOf(char[] array, char searchTerm) { private int indexOf(char[] array, char searchTerm) {
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < wordLength; i++) {
if (array[i] == searchTerm) { if (array[i] == searchTerm) {
return i; return i;
} }
@ -50,36 +51,36 @@ public class Game {
return -1; return -1;
} }
public static boolean isCorrect(int[] result) { public boolean isCorrect(int[] result) {
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < wordLength; i++) {
if (result[i] != CORRECT_CHARACTER) { if (result[i] != CORRECT_CHARACTER_POSITION) {
return false; return false;
} }
} }
return true; return true;
} }
public static boolean isInvalid(int[] result) { public boolean isInvalid(int[] result) {
return Arrays.equals(result, INVALID_GUESS); return Arrays.equals(result, invalidGuess);
} }
public int[] evaluate(String guess) { public int[] evaluate(String guess) {
if (!acceptableGuesses.contains(guess)) { if (!acceptableGuesses.contains(guess)) {
return INVALID_GUESS; return invalidGuess;
} }
// the guess is acceptable // the guess is acceptable
int[] result = new int[WORD_LENGTH]; int[] result = new int[wordLength];
char[] remaining = new char[WORD_LENGTH]; char[] remaining = new char[wordLength];
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < wordLength; i++) {
if (guess.charAt(i) == word.charAt(i)) { if (guess.charAt(i) == word.charAt(i)) {
result[i] = CORRECT_CHARACTER; result[i] = CORRECT_CHARACTER_POSITION;
} else { } else {
result[i] = INCORRECT_CHARACTER; result[i] = INCORRECT_CHARACTER;
remaining[i] = word.charAt(i); remaining[i] = word.charAt(i);
} }
} }
for (int i = 0; i < WORD_LENGTH; i++) { for (int i = 0; i < wordLength; i++) {
if (result[i] == INCORRECT_CHARACTER) { if (result[i] == INCORRECT_CHARACTER) {
int index = indexOf(remaining, guess.charAt(i)); int index = indexOf(remaining, guess.charAt(i));
if (index != -1) { if (index != -1) {
@ -103,6 +104,10 @@ public class Game {
return playerTwo; return playerTwo;
} }
public int getWordLength() {
return wordLength;
}
public String newGame() { public String newGame() {
Collections.shuffle(possibleWords); Collections.shuffle(possibleWords);
wordIndex = 0; wordIndex = 0;
@ -134,4 +139,13 @@ public class Game {
this.possibleWords = value; 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)) { final BufferedReader bufferedReader = new BufferedReader(streamReader)) {
String line = null; String line = null;
while ((line = bufferedReader.readLine()) != null) { while ((line = bufferedReader.readLine()) != null) {
c.add(line); c.add(line.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 playerReport = new Report();
final Report opponentReport = new Report(); final Report opponentReport = new Report();
playerReport.setGuess(guess); playerReport.setGuess(guess);
if (Game.isCorrect(result)) { if (game.isCorrect(result)) {
final String newWord = game.newWord(); final String newWord = game.newWord();
final String firstLetter = String.valueOf(newWord.charAt(0)); final String firstLetter = String.valueOf(newWord.charAt(0));
log.info("New word: {}", newWord); log.info("New word: {}", newWord);
@ -108,8 +108,17 @@ public class LingoController {
sendToPlayer(opponent, Destinations.OPPONENT_REPORTS, opponentReport); sendToPlayer(opponent, Destinations.OPPONENT_REPORTS, opponentReport);
} }
@MessageMapping("/hostGame") @MessageMapping("/hostGame5")
public synchronized void hostGame(@Header(SESSION_ID_HEADER) String sessionId) { 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 Player player = sessionManager.getPlayer(sessionId);
final String username = player.getUsername(); final String username = player.getUsername();
if (username == null) { if (username == null) {
@ -120,7 +129,9 @@ public class LingoController {
log.warn("{} is in a game already", player); log.warn("{} is in a game already", player);
return; 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); gameById.put(game.getId(), game);
gameByPlayer.put(player, game); gameByPlayer.put(player, game);
log.info("{} hosted Game {}", player, game.getId()); log.info("{} hosted Game {}", player, game.getId());
@ -152,9 +163,6 @@ public class LingoController {
// Start the game immediately // Start the game immediately
// TODO: require the players to "ready up" // TODO: require the players to "ready up"
game.setAcceptableGuesses(wordRepo.getGuesses());
game.setPossibleWords(wordRepo.getWords());
final String firstWord = game.newGame(); final String firstWord = game.newGame();
final String firstLetter = String.valueOf(firstWord.charAt(0)); final String firstLetter = String.valueOf(firstWord.charAt(0));
log.info("First word: {}", firstWord); log.info("First word: {}", firstWord);

View File

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

View File

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

View File

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

View File

@ -32,13 +32,14 @@
<strong>{{ game.playerOne }}</strong> vs. <strong>{{ game.playerTwo }}</strong> <strong>{{ game.playerOne }}</strong> vs. <strong>{{ game.playerTwo }}</strong>
</div> </div>
<button v-else v-bind:id="'game-' + game.id" @click="joinGame" type="button" v-bind:disabled="inGame" class="list-group-item"> <button v-else v-bind:id="'game-' + game.id" @click="joinGame" type="button" v-bind:disabled="inGame" class="list-group-item">
<strong>{{ game.playerOne }}</strong> wants to play <strong>{{ game.playerOne }}</strong> wants to play a {{ game.wordLength }}-letter game
</button> </button>
</template> </template>
</div> </div>
</div> </div>
<button v-show="inGame" @click="leaveGame" type="button" class="leave button">Leave game</button> <button v-show="inGame" @click="leaveGame" type="button" class="leave button">Leave game</button>
<button v-show="!inGame" @click="hostGame" type="button" class="create button">Create game</button> <button v-show="!inGame" @click="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> </div>
<div v-bind:class="{ primary: inStartedGame }" class="game column"> <div v-bind:class="{ primary: inStartedGame }" class="game column">