Initial commit

This commit is contained in:
Alex Page 2023-09-14 17:39:18 -04:00
commit 4148ec387b
11 changed files with 471529 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "word-engine"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.106"
thiserror = "1.0.48"

135
bag.json Normal file
View file

@ -0,0 +1,135 @@
{
"blank_tiles" : 2,
"tiles": [
{
"letter": "E",
"value": 1,
"count": 12
},
{
"letter": "A",
"value": 1,
"count": 9
},
{
"letter": "I",
"value": 1,
"count": 9
},
{
"letter": "O",
"value": 1,
"count": 8
},
{
"letter": "N",
"value": 1,
"count": 6
},
{
"letter": "R",
"value": 1,
"count": 6
},
{
"letter": "T",
"value": 1,
"count": 6
},
{
"letter": "L",
"value": 1,
"count": 4
},
{
"letter": "S",
"value": 1,
"count": 4
},
{
"letter": "U",
"value": 1,
"count": 4
},
{
"letter": "D",
"value": 2,
"count": 4
},
{
"letter": "G",
"value": 2,
"count": 3
},
{
"letter": "B",
"value": 3,
"count": 2
},
{
"letter": "C",
"value": 3,
"count": 2
},
{
"letter": "M",
"value": 3,
"count": 2
},
{
"letter": "P",
"value": 3,
"count": 2
},
{
"letter": "F",
"value": 4,
"count": 2
},
{
"letter": "H",
"value": 4,
"count": 2
},
{
"letter": "V",
"value": 4,
"count": 2
},
{
"letter": "W",
"value": 4,
"count": 2
},
{
"letter": "Y",
"value": 4,
"count": 2
},
{
"letter": "K",
"value": 5,
"count": 1
},
{
"letter": "J",
"value": 8,
"count": 1
},
{
"letter": "X",
"value": 8,
"count": 1
},
{
"letter": "Q",
"value": 10,
"count": 1
},
{
"letter": "Z",
"value": 10,
"count": 1
}
]
}

279077
lexicons/CSW22.txt Normal file

File diff suppressed because it is too large Load diff

191852
lexicons/NWL2020.txt Normal file

File diff suppressed because it is too large Load diff

73
src/baggy.rs Normal file
View file

@ -0,0 +1,73 @@
use std::fmt::{self, Display, Formatter};
use thiserror::Error;
use crate::data::BagData;
#[derive(Error, Debug)]
pub enum BagError {
#[error("Not enough tiles")]
NotEnoughTiles,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tile {
letter: char,
value: u8,
}
impl Tile {
pub fn letter(&self) -> char {
self.letter
}
pub fn value(&self) -> u8 {
self.value
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Bag {
tiles: Vec<Tile>,
}
impl Bag {
pub fn take(&mut self, count: usize) -> Result<Bag, BagError> {
if count > self.tiles.len() {
return Err(BagError::NotEnoughTiles);
}
let mut tiles = Vec::new();
for _ in 0..count {
let random_tile = self
.tiles
.remove(rand::random::<usize>() % self.tiles.len());
tiles.push(random_tile);
}
Ok(Bag { tiles })
}
}
impl From<BagData> for Bag {
fn from(data: BagData) -> Self {
let mut tiles = Vec::new();
for tile in data.tiles() {
for _ in 0..tile.count() {
tiles.push(Tile {
letter: tile.letter(),
value: tile.value(),
});
}
}
Bag { tiles }
}
}
impl Display for Bag {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Bag {{ tiles: [")?;
for tile in &self.tiles {
write!(f, "{}, ", tile.letter)?;
}
write!(f, "] }}")
}
}

183
src/board.rs Normal file
View file

@ -0,0 +1,183 @@
use thiserror::Error;
use crate::{baggy::Tile, dictionary::Dictionary};
#[derive(Error, Debug)]
pub enum MoveError {
#[error("Out of bounds")]
OutOfBounds,
#[error("Occupied")]
Occupied,
}
#[derive(Error, Debug)]
pub enum BoardError {
#[error("Invalid space")]
MoveError(#[from] MoveError),
}
pub enum MoveResult {
Valid(u32),
Invalid,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum SpaceKind {
#[default]
Normal,
Start,
DoubleLetter,
TripleLetter,
DoubleWord,
TripleWord,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Space {
pub kind: SpaceKind,
pub tile: Option<Tile>,
}
#[derive(Clone, Debug, PartialEq)]
struct Move {
x: usize,
y: usize,
tile: Tile,
}
#[derive(Debug)]
pub struct Board {
board: Vec<Vec<Space>>,
pending_move: Vec<Move>,
dictionary: Dictionary,
}
impl Board {
pub fn new(dictionary: Dictionary) -> Board {
let mut board = vec![vec![Space::default(); 15]; 15];
// Start space
board[7][7].kind = SpaceKind::Start;
// Triple word spaces
board[0][0].kind = SpaceKind::TripleWord;
board[0][7].kind = SpaceKind::TripleWord;
board[0][14].kind = SpaceKind::TripleWord;
board[7][0].kind = SpaceKind::TripleWord;
board[7][14].kind = SpaceKind::TripleWord;
board[14][0].kind = SpaceKind::TripleWord;
board[14][7].kind = SpaceKind::TripleWord;
board[14][14].kind = SpaceKind::TripleWord;
// Double word spaces
board[1][1].kind = SpaceKind::DoubleWord;
board[2][2].kind = SpaceKind::DoubleWord;
board[3][3].kind = SpaceKind::DoubleWord;
board[4][4].kind = SpaceKind::DoubleWord;
board[10][10].kind = SpaceKind::DoubleWord;
board[11][11].kind = SpaceKind::DoubleWord;
board[12][12].kind = SpaceKind::DoubleWord;
board[13][13].kind = SpaceKind::DoubleWord;
board[1][13].kind = SpaceKind::DoubleWord;
board[2][12].kind = SpaceKind::DoubleWord;
board[3][11].kind = SpaceKind::DoubleWord;
board[4][10].kind = SpaceKind::DoubleWord;
board[10][4].kind = SpaceKind::DoubleWord;
board[11][3].kind = SpaceKind::DoubleWord;
board[12][2].kind = SpaceKind::DoubleWord;
board[13][1].kind = SpaceKind::DoubleWord;
// Triple letter spaces
board[1][5].kind = SpaceKind::TripleLetter;
board[1][9].kind = SpaceKind::TripleLetter;
board[5][1].kind = SpaceKind::TripleLetter;
board[5][5].kind = SpaceKind::TripleLetter;
board[5][9].kind = SpaceKind::TripleLetter;
board[5][13].kind = SpaceKind::TripleLetter;
board[9][1].kind = SpaceKind::TripleLetter;
board[9][5].kind = SpaceKind::TripleLetter;
board[9][9].kind = SpaceKind::TripleLetter;
board[9][13].kind = SpaceKind::TripleLetter;
board[13][5].kind = SpaceKind::TripleLetter;
board[13][9].kind = SpaceKind::TripleLetter;
// Double letter spaces
board[0][3].kind = SpaceKind::DoubleLetter;
board[0][11].kind = SpaceKind::DoubleLetter;
board[2][6].kind = SpaceKind::DoubleLetter;
board[2][8].kind = SpaceKind::DoubleLetter;
board[3][0].kind = SpaceKind::DoubleLetter;
board[3][7].kind = SpaceKind::DoubleLetter;
board[3][14].kind = SpaceKind::DoubleLetter;
board[6][2].kind = SpaceKind::DoubleLetter;
board[6][6].kind = SpaceKind::DoubleLetter;
board[6][8].kind = SpaceKind::DoubleLetter;
board[6][12].kind = SpaceKind::DoubleLetter;
board[7][3].kind = SpaceKind::DoubleLetter;
board[7][11].kind = SpaceKind::DoubleLetter;
board[8][2].kind = SpaceKind::DoubleLetter;
board[8][6].kind = SpaceKind::DoubleLetter;
board[8][8].kind = SpaceKind::DoubleLetter;
board[8][12].kind = SpaceKind::DoubleLetter;
board[11][0].kind = SpaceKind::DoubleLetter;
board[11][7].kind = SpaceKind::DoubleLetter;
board[11][14].kind = SpaceKind::DoubleLetter;
board[12][6].kind = SpaceKind::DoubleLetter;
board[12][8].kind = SpaceKind::DoubleLetter;
board[14][3].kind = SpaceKind::DoubleLetter;
board[14][11].kind = SpaceKind::DoubleLetter;
Board {
board,
pending_move: Vec::new(),
dictionary,
}
}
fn check_space_for_move(&self) -> Result<(), MoveError> {
todo!()
}
pub fn place_tile(&mut self, x: usize, y: usize, tile: Tile) -> Result<(), BoardError> {
self.check_space_for_move()?;
self.pending_move.push(Move { x, y, tile });
Ok(())
}
pub fn validate_move(&self) -> MoveResult {
todo!()
}
pub fn commit_move() -> MoveResult {
todo!()
}
}
/// Print the board as a grid
impl std::fmt::Display for Board {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Board {{")?;
for row in &self.board {
write!(f, " [")?;
let mut row = row.iter().peekable();
while let Some(space) = row.next() {
match space.tile {
Some(tile) => write!(f, "{}", tile.letter())?,
None => match space.kind {
SpaceKind::Normal => write!(f, "")?,
SpaceKind::Start => write!(f, "")?,
SpaceKind::DoubleLetter => write!(f, "2")?,
SpaceKind::TripleLetter => write!(f, "3")?,
SpaceKind::DoubleWord => write!(f, "")?,
SpaceKind::TripleWord => write!(f, "")?,
},
}
if row.peek().is_some() {
write!(f, " ")?;
}
}
writeln!(f, "]")?;
}
write!(f, "}}")
}
}

37
src/data.rs Normal file
View file

@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct TileData {
letter: char,
value: u8,
count: u8,
}
impl TileData {
pub fn letter(&self) -> char {
self.letter
}
pub fn value(&self) -> u8 {
self.value
}
pub fn count(&self) -> u8 {
self.count
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BagData {
tiles: Vec<TileData>,
}
impl BagData {
pub fn load_from_json(json: &str) -> serde_json::Result<Self> {
serde_json::from_str(json)
}
pub fn tiles(&self) -> &Vec<TileData> {
&self.tiles
}
}

42
src/dictionary.rs Normal file
View file

@ -0,0 +1,42 @@
use std::fmt::Display;
use crate::baggy::Tile;
#[derive(Clone, Debug, PartialEq)]
pub struct Word {
tiles: Vec<Tile>,
}
impl Display for Word {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for tile in &self.tiles {
write!(f, "{}", tile.letter())?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Dictionary {
words: Vec<String>,
}
impl Dictionary {
pub fn new() -> Self {
let words = include_str!("../lexicons/CSW22.txt");
Dictionary {
words: words.lines().map(|x| x.to_string()).collect(),
}
}
/// Checks if a word is in the dictionary
pub fn contains(&self, word: Word) -> bool {
self.words.contains(&word.to_string())
}
}
impl Default for Dictionary {
fn default() -> Self {
Self::new()
}
}

83
src/lib.rs Normal file
View file

@ -0,0 +1,83 @@
use player::PlayerId;
use thiserror::Error;
mod baggy;
mod board;
mod data;
mod dictionary;
mod player;
#[derive(Debug, Error)]
pub enum GameError {
#[error("Not enough tiles in bag")]
NotEnoughTiles(#[from] baggy::BagError),
}
#[derive(Debug)]
pub struct Game {
board: board::Board,
bag: baggy::Bag,
players: Vec<player::Player>,
}
impl Game {
pub fn new() -> Game {
let bag_data = data::BagData::load_from_json(include_str!("../bag.json")).unwrap();
let bag = baggy::Bag::from(bag_data);
let dictionary = dictionary::Dictionary::new();
Game {
board: board::Board::new(dictionary),
bag,
players: Vec::new(),
}
}
pub fn players(&self) -> &Vec<player::Player> {
&self.players
}
pub fn bag(&self) -> &baggy::Bag {
&self.bag
}
pub fn board(&self) -> &board::Board {
&self.board
}
pub fn add_player(&mut self) -> Result<PlayerId, GameError> {
let id = PlayerId(self.players.len() as u32);
self.players
.push(player::Player::new(id, self.bag.take(7)?));
Ok(id)
}
pub fn add_players(&mut self, count: usize) -> Result<Vec<PlayerId>, GameError> {
let mut ids = Vec::new();
for _ in 0..count {
ids.push(self.add_player()?);
}
Ok(ids)
}
}
impl Default for Game {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::Game;
#[test]
fn test_game() {
let mut game = Game::new();
game.add_players(2).unwrap();
for player in game.players() {
println!("Player bag: {}", player.bag());
}
println!("Game bag: {}", game.bag());
println!("Game board: {}", game.board());
}
}

33
src/player.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::baggy::Bag;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlayerId(pub u32);
#[derive(Debug)]
pub struct Player {
id: PlayerId,
score: u32,
bag: Bag,
}
impl Player {
pub fn new(id: PlayerId, bag: Bag) -> Self {
Player { id, score: 0, bag }
}
pub fn id(&self) -> PlayerId {
self.id
}
pub fn score(&self) -> u32 {
self.score
}
pub fn bag(&self) -> &Bag {
&self.bag
}
pub fn give_points(&mut self, points: u32) {
self.score = self.score.saturating_add(points);
}
}