Initial commit
This commit is contained in:
commit
4148ec387b
11 changed files with 471529 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal 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
135
bag.json
Normal 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
279077
lexicons/CSW22.txt
Normal file
File diff suppressed because it is too large
Load diff
191852
lexicons/NWL2020.txt
Normal file
191852
lexicons/NWL2020.txt
Normal file
File diff suppressed because it is too large
Load diff
73
src/baggy.rs
Normal file
73
src/baggy.rs
Normal 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
183
src/board.rs
Normal 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
37
src/data.rs
Normal 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
42
src/dictionary.rs
Normal 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
83
src/lib.rs
Normal 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
33
src/player.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue