Integrate very WIP move-checking onto front-end
This commit is contained in:
parent
cbd76895ac
commit
a496b15710
7 changed files with 163 additions and 31 deletions
36
src/board.rs
36
src/board.rs
|
@ -24,6 +24,14 @@ impl Direction {
|
||||||
pub struct Coordinates (pub u8, pub u8);
|
pub struct Coordinates (pub u8, pub u8);
|
||||||
|
|
||||||
impl Coordinates {
|
impl Coordinates {
|
||||||
|
|
||||||
|
pub fn new_from_index(index: usize) -> Self {
|
||||||
|
let y = index / GRID_LENGTH as usize;
|
||||||
|
let x = index % GRID_LENGTH as usize;
|
||||||
|
|
||||||
|
Coordinates(x as u8, y as u8)
|
||||||
|
}
|
||||||
|
|
||||||
fn add(&self, direction: Direction, i: i8) -> Option<Self> {
|
fn add(&self, direction: Direction, i: i8) -> Option<Self> {
|
||||||
let proposed = match direction {
|
let proposed = match direction {
|
||||||
Direction::Column => {(self.0 as i8, self.1 as i8+i)}
|
Direction::Column => {(self.0 as i8, self.1 as i8+i)}
|
||||||
|
@ -89,6 +97,7 @@ impl Letter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize)]
|
#[derive(Debug, Copy, Clone, Serialize)]
|
||||||
|
@ -101,14 +110,14 @@ pub enum CellType {
|
||||||
Start,
|
Start,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
pub value: Option<Letter>,
|
pub value: Option<Letter>,
|
||||||
pub cell_type: CellType,
|
pub cell_type: CellType,
|
||||||
pub coordinates: Coordinates,
|
pub coordinates: Coordinates,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
pub cells: Vec<Cell>,
|
pub cells: Vec<Cell>,
|
||||||
}
|
}
|
||||||
|
@ -376,8 +385,6 @@ impl Board {
|
||||||
} else {
|
} else {
|
||||||
return Err("Played tiles must be anchored to something")
|
return Err("Played tiles must be anchored to something")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_word_at_position(&self, mut start_coords: Coordinates, direction: Direction) -> Option<Word> {
|
fn find_word_at_position(&self, mut start_coords: Coordinates, direction: Direction) -> Option<Word> {
|
||||||
|
@ -431,6 +438,25 @@ impl Board {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn receive_play(&mut self, play: Vec<(Letter, Coordinates)>) -> Result<(), String> {
|
||||||
|
for (mut letter, coords) in play {
|
||||||
|
{
|
||||||
|
let mut cell = match self.get_cell_mut(coords) {
|
||||||
|
Ok(cell) => {cell}
|
||||||
|
Err(e) => {return Err(e.to_string())}
|
||||||
|
};
|
||||||
|
|
||||||
|
if cell.value.is_some() {
|
||||||
|
return Err(format!("There's already a letter at {:?}", coords));
|
||||||
|
}
|
||||||
|
|
||||||
|
letter.ephemeral = true;
|
||||||
|
cell.value = Some(letter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,7 +800,7 @@ mod tests {
|
||||||
board.get_cell_mut(Coordinates(6, 0)).unwrap().value = Some(Letter::new_fixed('L', 1));
|
board.get_cell_mut(Coordinates(6, 0)).unwrap().value = Some(Letter::new_fixed('L', 1));
|
||||||
|
|
||||||
fn check_board(board: &mut Board, inverted: bool) {
|
fn check_board(board: &mut Board, inverted: bool) {
|
||||||
let dictionary = DictionaryImpl::create("resources/dictionary.csv");
|
let dictionary = DictionaryImpl::create_from_path("resources/dictionary.csv");
|
||||||
println!("{}", board);
|
println!("{}", board);
|
||||||
let words = board.find_played_words();
|
let words = board.find_played_words();
|
||||||
match words {
|
match words {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use csv::Reader;
|
||||||
use crate::board::Word;
|
use crate::board::Word;
|
||||||
|
|
||||||
pub trait Dictionary {
|
pub trait Dictionary {
|
||||||
fn create(path: &str) -> Self;
|
|
||||||
|
fn create_from_reader<T: std::io::Read>(reader: Reader<T>) -> Self;
|
||||||
|
fn create_from_path(path: &str) -> Self;
|
||||||
|
fn create_from_str(data: &str) -> Self;
|
||||||
fn filter_to_sub_dictionary(&self, proportion: f64) -> Self;
|
fn filter_to_sub_dictionary(&self, proportion: f64) -> Self;
|
||||||
fn substring_set(&self) -> HashSet<&str>;
|
fn substring_set(&self) -> HashSet<&str>;
|
||||||
fn is_word_valid(&self, word: &Word) -> bool;
|
fn is_word_valid(&self, word: &Word) -> bool;
|
||||||
|
@ -12,8 +16,7 @@ pub type DictionaryImpl = HashMap<String, f64>;
|
||||||
|
|
||||||
impl Dictionary for DictionaryImpl{
|
impl Dictionary for DictionaryImpl{
|
||||||
|
|
||||||
fn create(path: &str) -> Self {
|
fn create_from_reader<T: std::io::Read>(mut reader: Reader<T>) -> Self {
|
||||||
let mut reader = csv::Reader::from_path(path).unwrap();
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
for result in reader.records() {
|
for result in reader.records() {
|
||||||
|
@ -30,6 +33,20 @@ impl Dictionary for DictionaryImpl{
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_from_path(path: &str) -> Self {
|
||||||
|
let reader = csv::Reader::from_path(path).unwrap();
|
||||||
|
|
||||||
|
DictionaryImpl::create_from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_from_str(data: &str) -> Self {
|
||||||
|
let reader = csv::ReaderBuilder::new()
|
||||||
|
.has_headers(true)
|
||||||
|
.from_reader(data.as_bytes());
|
||||||
|
|
||||||
|
DictionaryImpl::create_from_reader(reader)
|
||||||
|
}
|
||||||
|
|
||||||
fn filter_to_sub_dictionary(&self, proportion: f64) -> Self {
|
fn filter_to_sub_dictionary(&self, proportion: f64) -> Self {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
@ -71,7 +88,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dictionary() {
|
fn test_dictionary() {
|
||||||
let dictionary = HashMap::create("resources/dictionary.csv");
|
let dictionary = HashMap::create_from_path("resources/dictionary.csv");
|
||||||
|
|
||||||
assert_eq!(dictionary.len(), 279429);
|
assert_eq!(dictionary.len(), 279429);
|
||||||
|
|
||||||
|
|
16
src/game.rs
16
src/game.rs
|
@ -1,8 +1,8 @@
|
||||||
use rand::rngs::SmallRng;
|
use rand::rngs::SmallRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
|
||||||
use crate::board::{Board, Letter};
|
use crate::board::{Board, Letter};
|
||||||
use crate::constants::{standard_tile_pool, TRAY_LENGTH};
|
use crate::constants::{standard_tile_pool, TRAY_LENGTH};
|
||||||
|
use crate::dictionary::{Dictionary, DictionaryImpl};
|
||||||
use crate::player_interaction::ai::Difficulty;
|
use crate::player_interaction::ai::Difficulty;
|
||||||
use crate::player_interaction::Tray;
|
use crate::player_interaction::Tray;
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ pub struct Game {
|
||||||
tile_pool: Vec<Letter>,
|
tile_pool: Vec<Letter>,
|
||||||
board: Board,
|
board: Board,
|
||||||
player: PlayerState,
|
player: PlayerState,
|
||||||
|
dictionary: DictionaryImpl,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Problem - I want to provide a UI to the player
|
// Problem - I want to provide a UI to the player
|
||||||
|
@ -37,7 +38,7 @@ pub struct Game {
|
||||||
// This will later work out well when I build it out as an API for multiplayer.
|
// This will later work out well when I build it out as an API for multiplayer.
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new(seed: u64) -> Self {
|
pub fn new(seed: u64, dictionary_text: &str) -> Self {
|
||||||
let mut rng = SmallRng::seed_from_u64(seed);
|
let mut rng = SmallRng::seed_from_u64(seed);
|
||||||
|
|
||||||
let mut letters = standard_tile_pool(Some(&mut rng));
|
let mut letters = standard_tile_pool(Some(&mut rng));
|
||||||
|
@ -55,6 +56,7 @@ impl Game {
|
||||||
tile_pool: letters,
|
tile_pool: letters,
|
||||||
board: Board::new(),
|
board: Board::new(),
|
||||||
player,
|
player,
|
||||||
|
dictionary: DictionaryImpl::create_from_str(dictionary_text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,4 +70,14 @@ impl Game {
|
||||||
|
|
||||||
pub fn get_board(&self) -> &Board {&self.board}
|
pub fn get_board(&self) -> &Board {&self.board}
|
||||||
|
|
||||||
|
pub fn get_board_mut(&mut self) -> &mut Board {
|
||||||
|
&mut self.board
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dictionary(&self) -> &DictionaryImpl {
|
||||||
|
&self.dictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
53
src/wasm.rs
53
src/wasm.rs
|
@ -1,18 +1,28 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_wasm_bindgen::Error;
|
use serde_wasm_bindgen::Error;
|
||||||
|
use tsify::Tsify;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
use crate::board::CellType;
|
use crate::board::{Board, CellType, Coordinates, Letter};
|
||||||
use crate::game::Game;
|
use crate::game::Game;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct GameWasm(Game);
|
pub struct GameWasm(Game);
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize, Tsify, Copy, Clone)]
|
||||||
|
#[tsify(from_wasm_abi)]
|
||||||
|
pub struct PlayedTile {
|
||||||
|
index: usize,
|
||||||
|
character: Option<char>,
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl GameWasm {
|
impl GameWasm {
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(seed: u64) -> GameWasm {
|
pub fn new(seed: u64, dictionary_text: &str) -> GameWasm {
|
||||||
GameWasm(Game::new(seed))
|
GameWasm(Game::new(seed, dictionary_text))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tray(&self) -> Result<JsValue, Error> {
|
pub fn get_tray(&self) -> Result<JsValue, Error> {
|
||||||
|
@ -30,4 +40,41 @@ impl GameWasm {
|
||||||
|
|
||||||
serde_wasm_bindgen::to_value(&cell_types)
|
serde_wasm_bindgen::to_value(&cell_types)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn receive_play(&mut self, tray_tile_locations: JsValue, commit_move: bool) -> Result<JsValue, JsValue> {
|
||||||
|
let tray_tile_locations: Vec<Option<PlayedTile>> = serde_wasm_bindgen::from_value(tray_tile_locations)?;
|
||||||
|
|
||||||
|
let mut board_instance = self.0.get_board().clone();
|
||||||
|
let tray = self.0.get_tray().clone();
|
||||||
|
|
||||||
|
let mut played_letters: Vec<(Letter, Coordinates)> = Vec::new();
|
||||||
|
for (i, played_tile) in tray_tile_locations.iter().enumerate() {
|
||||||
|
if played_tile.is_some() {
|
||||||
|
let played_tile = played_tile.unwrap();
|
||||||
|
let mut letter: Letter = tray.letters.get(i).unwrap().unwrap();
|
||||||
|
|
||||||
|
let coord = Coordinates::new_from_index(played_tile.index);
|
||||||
|
if letter.is_blank {
|
||||||
|
match played_tile.character {
|
||||||
|
None => {
|
||||||
|
panic!("You can't play a blank character without providing a letter value")
|
||||||
|
}
|
||||||
|
Some(x) => {
|
||||||
|
// TODO - I should check that the character is a valid letter
|
||||||
|
letter.text = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
played_letters.push((letter, coord));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
board_instance.receive_play(played_letters)?;
|
||||||
|
|
||||||
|
let result = board_instance.calculate_scores(self.0.get_dictionary())?;
|
||||||
|
|
||||||
|
Ok(serde_wasm_bindgen::to_value(&&result.1).unwrap())
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {GameWasm, Letter as LetterData, Tray} from "word_grid";
|
import {GameWasm, Letter as LetterData, PlayedTile, Tray} from "word_grid";
|
||||||
import {createRoot} from "react-dom/client";
|
import {createRoot} from "react-dom/client";
|
||||||
import {Children, useMemo, useReducer, useState} from "react";
|
import {Children, useMemo, useReducer, useState} from "react";
|
||||||
|
|
||||||
|
@ -121,17 +121,29 @@ export function Game(props: {wasm: GameWasm}) {
|
||||||
<Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={dispatch}/>
|
<Grid cellTypes={cellTypes} playerLetters={playerLetters} dispatch={dispatch}/>
|
||||||
<TileTray letters={playerLetters} trayLength={7} dispatch={dispatch}/>
|
<TileTray letters={playerLetters} trayLength={7} dispatch={dispatch}/>
|
||||||
<button onClick={(e) => {
|
<button onClick={(e) => {
|
||||||
dispatch({
|
const playedTiles = playerLetters.map((i) => {
|
||||||
"start": {
|
if (i === undefined) {
|
||||||
"location": LocationType.TRAY,
|
return null;
|
||||||
"index": 0
|
}
|
||||||
},
|
|
||||||
"end": {
|
if (i.location === LocationType.GRID) {
|
||||||
"location": LocationType.GRID,
|
let result: PlayedTile = {
|
||||||
"index": 3
|
index: i.index,
|
||||||
},
|
character: undefined
|
||||||
})
|
};
|
||||||
}}>Swap two</button>
|
if (i.is_blank) {
|
||||||
|
result.character = i.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = props.wasm.receive_play(playedTiles, false);
|
||||||
|
console.log({result});
|
||||||
|
}}>Check Submission</button>
|
||||||
</>;
|
</>;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import init, {greet, GameWasm} from '../node_modules/word_grid/word_grid.js';
|
import init, {greet, GameWasm} from '../node_modules/word_grid/word_grid.js';
|
||||||
import {Game, LocationType, PlayableLetterData, TileTray} from "./elements";
|
import {Game} from "./elements";
|
||||||
import {createRoot} from "react-dom/client";
|
import {createRoot} from "react-dom/client";
|
||||||
import {Tray} from "word_grid";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const dictionary_url = new URL("../../resources/dictionary.csv", import.meta.url);
|
||||||
|
|
||||||
|
let dictionary_text: string = null;
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
// First up we need to actually load the wasm file, so we use the
|
// First up we need to actually load the wasm file, so we use the
|
||||||
// default export to inform it where the wasm file is located on the
|
// default export to inform it where the wasm file is located on the
|
||||||
|
@ -33,7 +37,17 @@ async function run() {
|
||||||
// modes
|
// modes
|
||||||
await init();
|
await init();
|
||||||
|
|
||||||
let game = new GameWasm(BigInt(1234));
|
// need to retrieve the dictionary.csv file as text
|
||||||
|
if(dictionary_text == null){
|
||||||
|
|
||||||
|
dictionary_text = await fetch(dictionary_url).then((response) => {
|
||||||
|
return response.text()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let game = new GameWasm(BigInt(1234), dictionary_text);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const cellTypes = game.get_board_cell_types();
|
const cellTypes = game.get_board_cell_types();
|
||||||
console.log({cellTypes});
|
console.log({cellTypes});
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ephemeral {
|
||||||
|
opacity: 75%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-spot-normal{
|
.grid-spot-normal{
|
||||||
|
|
Loading…
Reference in a new issue