Add end game conditions
Also fix some UI bugs
This commit is contained in:
parent
0d30ac0b46
commit
e0fe22e9ce
6 changed files with 302 additions and 77 deletions
139
src/game.rs
139
src/game.rs
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::SeedableRng;
|
||||
|
@ -123,6 +125,18 @@ impl PlayerStates {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize, Tsify, Debug, Clone)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum GameState {
|
||||
InProgress,
|
||||
Ended {
|
||||
finisher: Option<String>,
|
||||
remaining_tiles: HashMap<String, Vec<Letter>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Game{
|
||||
pub tile_pool: Vec<Letter>,
|
||||
rng: SmallRng,
|
||||
|
@ -130,6 +144,8 @@ pub struct Game{
|
|||
pub player_states: PlayerStates,
|
||||
dictionary: DictionaryImpl,
|
||||
turn_order: usize,
|
||||
turns_not_played: usize,
|
||||
state: GameState,
|
||||
}
|
||||
|
||||
|
||||
|
@ -183,6 +199,8 @@ impl Game {
|
|||
player_states: PlayerStates(player_states),
|
||||
dictionary,
|
||||
turn_order: 0,
|
||||
turns_not_played: 0,
|
||||
state: GameState::InProgress,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,7 +212,7 @@ impl Game {
|
|||
self.board = new_board;
|
||||
}
|
||||
|
||||
pub fn fill_trays(&mut self){
|
||||
fn fill_trays(&mut self){
|
||||
for state in self.player_states.0.iter_mut() {
|
||||
let tray = &mut state.tray;
|
||||
tray.fill(&mut self.tile_pool);
|
||||
|
@ -205,7 +223,15 @@ impl Game {
|
|||
&self.dictionary
|
||||
}
|
||||
|
||||
pub fn receive_play(&mut self, tray_tile_locations: Vec<Option<PlayedTile>>, commit_move: bool) -> Result<TurnAction, String> {
|
||||
fn verify_game_in_progress(&self) -> Result<(), String> {
|
||||
if !matches!(self.state, GameState::InProgress) {
|
||||
return Err("Moves cannot be made after a game has finished".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn receive_play(&mut self, tray_tile_locations: Vec<Option<PlayedTile>>, commit_move: bool) -> Result<(TurnAction, GameState), String> {
|
||||
self.verify_game_in_progress()?;
|
||||
|
||||
let player = self.current_player_name();
|
||||
|
||||
|
@ -244,18 +270,26 @@ impl Game {
|
|||
self.set_board(board_instance);
|
||||
self.fill_trays();
|
||||
|
||||
self.increment_turn();
|
||||
self.increment_turn(true);
|
||||
|
||||
if self.get_player_tile_count(&player).unwrap() == 0 {
|
||||
// game is over
|
||||
self.end_game(Some(player));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(TurnAction::PlayTiles {
|
||||
Ok((TurnAction::PlayTiles {
|
||||
result: ScoreResult {
|
||||
words,
|
||||
total: total_score,
|
||||
},
|
||||
})
|
||||
}, self.state.clone()))
|
||||
}
|
||||
|
||||
pub fn exchange_tiles(&mut self, tray_tile_locations: Vec<bool>) -> Result<(Tray, TurnAction), String> {
|
||||
pub fn exchange_tiles(&mut self, tray_tile_locations: Vec<bool>) -> Result<(Tray, TurnAction, GameState), String> {
|
||||
self.verify_game_in_progress()?;
|
||||
|
||||
let player = self.current_player_name();
|
||||
let tray = match self.player_states.get_tray_mut(&player) {
|
||||
None => {return Err(format!("Player {} not found", player))}
|
||||
|
@ -285,9 +319,9 @@ impl Game {
|
|||
|
||||
let tray = tray.clone();
|
||||
|
||||
self.increment_turn();
|
||||
let state = self.increment_turn(false);
|
||||
|
||||
Ok((tray, TurnAction::ExchangeTiles { tiles_exchanged }))
|
||||
Ok((tray, TurnAction::ExchangeTiles { tiles_exchanged }, state.clone()))
|
||||
|
||||
}
|
||||
|
||||
|
@ -297,15 +331,66 @@ impl Game {
|
|||
self.dictionary.insert(word, -1.0);
|
||||
}
|
||||
|
||||
pub fn increment_turn(&mut self) {
|
||||
pub fn pass(&mut self) -> Result<GameState, String> {
|
||||
self.verify_game_in_progress()?;
|
||||
Ok(self.increment_turn(false).clone())
|
||||
}
|
||||
|
||||
fn increment_turn(&mut self, played: bool) -> &GameState{
|
||||
self.turn_order += 1;
|
||||
if !played {
|
||||
self.turns_not_played += 1;
|
||||
|
||||
// check if game has ended due to passing
|
||||
if self.turns_not_played >= 2*self.player_states.0.len() {
|
||||
self.end_game(None);
|
||||
}
|
||||
|
||||
} else {
|
||||
self.turns_not_played = 0;
|
||||
}
|
||||
|
||||
&self.state
|
||||
}
|
||||
|
||||
fn end_game(&mut self, finisher: Option<String>) {
|
||||
|
||||
let mut finished_letters_map = HashMap::new();
|
||||
let mut points_forfeit = 0;
|
||||
|
||||
for player in self.player_states.0.iter_mut() {
|
||||
let tray = &mut player.tray;
|
||||
let mut letters_remaining = Vec::new();
|
||||
let mut player_points_lost = 0;
|
||||
for letter in tray.letters.iter_mut() {
|
||||
if let Some(letter) = letter {
|
||||
player_points_lost += letter.points;
|
||||
letters_remaining.push(letter.clone());
|
||||
}
|
||||
*letter = None;
|
||||
}
|
||||
|
||||
points_forfeit += player_points_lost;
|
||||
player.score -= player_points_lost;
|
||||
finished_letters_map.insert(player.player.get_name().to_string(), letters_remaining);
|
||||
}
|
||||
|
||||
if let Some(finisher) = &finisher {
|
||||
let mut state = self.player_states.get_player_state_mut(finisher).unwrap();
|
||||
state.score += points_forfeit;
|
||||
}
|
||||
|
||||
self.state = GameState::Ended {
|
||||
finisher,
|
||||
remaining_tiles: finished_letters_map
|
||||
};
|
||||
}
|
||||
|
||||
pub fn current_player_name(&self) -> String {
|
||||
self.player_states.get_player_name_by_turn_id(self.turn_order).to_string()
|
||||
}
|
||||
|
||||
pub fn advance_turn(&mut self) -> Result<TurnAdvanceResult, String> {
|
||||
pub fn advance_turn(&mut self) -> Result<(TurnAdvanceResult, GameState), String> {
|
||||
let current_player = self.current_player_name();
|
||||
let state = self.player_states.get_player_state_mut(¤t_player).ok_or("There should be a player available")?;
|
||||
|
||||
|
@ -330,32 +415,32 @@ impl Game {
|
|||
}
|
||||
|
||||
if self.tile_pool.is_empty(){
|
||||
self.increment_turn();
|
||||
Ok(TurnAdvanceResult::AIMove {
|
||||
let game_state = self.increment_turn(false);
|
||||
Ok((TurnAdvanceResult::AIMove {
|
||||
name: current_player,
|
||||
action: TurnAction::Pass,
|
||||
})
|
||||
}, game_state.clone()))
|
||||
} else {
|
||||
let (_, action) = self.exchange_tiles(to_exchange)?;
|
||||
Ok(TurnAdvanceResult::AIMove {
|
||||
let (_, action, game_state) = self.exchange_tiles(to_exchange)?;
|
||||
Ok((TurnAdvanceResult::AIMove {
|
||||
name: current_player,
|
||||
action,
|
||||
})
|
||||
}, game_state))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some(best_move) => {
|
||||
let play = best_move.convert_to_play(tray);
|
||||
let action = self.receive_play(play, true)?;
|
||||
Ok(TurnAdvanceResult::AIMove {
|
||||
let (action, game_state) = self.receive_play(play, true)?;
|
||||
Ok((TurnAdvanceResult::AIMove {
|
||||
name: current_player,
|
||||
action,
|
||||
})
|
||||
}, game_state))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(TurnAdvanceResult::HumanInputRequired{name: self.current_player_name()})
|
||||
Ok((TurnAdvanceResult::HumanInputRequired{name: self.current_player_name()}, self.state.clone()))
|
||||
}
|
||||
|
||||
|
||||
|
@ -372,12 +457,12 @@ impl Game {
|
|||
};
|
||||
|
||||
Ok(
|
||||
tray.letters.iter()
|
||||
.filter(|l| l.is_some())
|
||||
.count()
|
||||
tray.count()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Tsify, Debug)]
|
||||
|
@ -426,13 +511,17 @@ mod tests {
|
|||
println!("Current player is {current_player}");
|
||||
assert_eq!(current_player, "Player");
|
||||
|
||||
game.increment_turn();
|
||||
assert_eq!(game.current_player_name(), "AI 0");
|
||||
assert_eq!(0, game.turns_not_played);
|
||||
game.increment_turn(false);
|
||||
assert_eq!(1, game.turns_not_played);
|
||||
|
||||
assert_eq!(game.current_player_name(), "AI");
|
||||
|
||||
let result = game.advance_turn();
|
||||
println!("AI move is {result:?}");
|
||||
|
||||
assert_eq!(game.current_player_name(), "Player");
|
||||
assert_eq!(0, game.turns_not_played);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,12 @@ impl Tray {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.letters.iter()
|
||||
.filter(|l| l.is_some())
|
||||
.count()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
56
src/wasm.rs
56
src/wasm.rs
|
@ -4,7 +4,7 @@ use tsify::Tsify;
|
|||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::board::{CellType, Letter};
|
||||
use crate::game::{Game, PlayedTile};
|
||||
use crate::game::{Game, GameState, PlayedTile};
|
||||
use crate::player_interaction::ai::Difficulty;
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -22,10 +22,12 @@ pub enum ResponseType {
|
|||
pub struct MyResult<E: Serialize> {
|
||||
response_type: ResponseType,
|
||||
value: E,
|
||||
game_state: Option<GameState>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl GameWasm {
|
||||
|
||||
|
@ -69,16 +71,18 @@ impl GameWasm {
|
|||
let result = self.0.receive_play(tray_tile_locations, commit_move);
|
||||
|
||||
match result {
|
||||
Ok(x) => {
|
||||
Ok((x, game_state)) => {
|
||||
serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::OK,
|
||||
value: x
|
||||
value: x,
|
||||
game_state: Some(game_state)
|
||||
})
|
||||
},
|
||||
Err(e) => {
|
||||
serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::ERR,
|
||||
value: e
|
||||
value: e,
|
||||
game_state: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -113,16 +117,18 @@ impl GameWasm {
|
|||
let tray_tile_locations: Vec<bool> = serde_wasm_bindgen::from_value(tray_tile_locations)?;
|
||||
|
||||
match self.0.exchange_tiles(tray_tile_locations) {
|
||||
Ok((_, turn_action)) => {
|
||||
Ok((_, turn_action, state)) => {
|
||||
serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::OK,
|
||||
value: turn_action
|
||||
value: turn_action,
|
||||
game_state: Some(state),
|
||||
})
|
||||
},
|
||||
Err(e) => {
|
||||
serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::ERR,
|
||||
value: e
|
||||
value: e,
|
||||
game_state: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -134,17 +140,43 @@ impl GameWasm {
|
|||
self.0.add_word(word);
|
||||
}
|
||||
|
||||
pub fn skip_turn(&mut self) {self.0.increment_turn()}
|
||||
pub fn skip_turn(&mut self) -> Result<JsValue, Error>{
|
||||
let result = self.0.pass();
|
||||
match result {
|
||||
Ok(game_state) => {
|
||||
Ok(serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::OK,
|
||||
value: "Turn passed",
|
||||
game_state: Some(game_state),
|
||||
})?)
|
||||
},
|
||||
Err(e) => {
|
||||
Ok(serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::ERR,
|
||||
value: e,
|
||||
game_state: None,
|
||||
})?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_turn(&mut self) -> Result<JsValue, Error> {
|
||||
let result = self.0.advance_turn();
|
||||
|
||||
match result {
|
||||
Ok(x) => {
|
||||
Ok(serde_wasm_bindgen::to_value(&x)?)
|
||||
Ok((turn_advance_result, game_state)) => {
|
||||
Ok(serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::OK,
|
||||
value: turn_advance_result,
|
||||
game_state: Some(game_state),
|
||||
})?)
|
||||
},
|
||||
Err(e) => {
|
||||
Ok(serde_wasm_bindgen::to_value(&e)?)
|
||||
Ok(serde_wasm_bindgen::to_value(&MyResult {
|
||||
response_type: ResponseType::ERR,
|
||||
value: e,
|
||||
game_state: None,
|
||||
})?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,12 +197,14 @@ impl GameWasm {
|
|||
Ok(serde_wasm_bindgen::to_value(&MyResult{
|
||||
response_type: ResponseType::OK,
|
||||
value: count,
|
||||
game_state: None,
|
||||
})?)
|
||||
},
|
||||
Err(msg) => {
|
||||
Ok(serde_wasm_bindgen::to_value(&MyResult{
|
||||
response_type: ResponseType::OK,
|
||||
value: msg,
|
||||
game_state: None,
|
||||
})?)
|
||||
}
|
||||
}
|
||||
|
|
158
ui/src/Game.tsx
158
ui/src/Game.tsx
|
@ -1,6 +1,7 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
GameWasm,
|
||||
GameState,
|
||||
GameWasm, Letter,
|
||||
Letter as LetterData,
|
||||
MyResult,
|
||||
PlayedTile,
|
||||
|
@ -38,6 +39,7 @@ export function Game(props: {
|
|||
return props.wasm.get_board_cell_types();
|
||||
}, []);
|
||||
|
||||
const [isGameOver, setGameOver] = useState<boolean>(false);
|
||||
const [confirmedScorePoints, setConfirmedScorePoints] = useState<number>(-1);
|
||||
|
||||
function movePlayableLetters(playerLetters: PlayableLetterData[], update: TileDispatchAction) {
|
||||
|
@ -89,13 +91,16 @@ export function Game(props: {
|
|||
function exchangeFunction(selectedArray: Array<boolean>) {
|
||||
|
||||
const result: MyResult<TurnAction | string> = props.wasm.exchange_tiles(selectedArray);
|
||||
console.log({result});
|
||||
|
||||
if(result.response_type === "ERR") {
|
||||
logDispatch(<div><em>{(result.value as string)}</em></div>);
|
||||
} else {
|
||||
handlePlayerAction(result.value as TurnAction, props.settings.playerName);
|
||||
setTurnCount(turnCount + 1);
|
||||
|
||||
if(result.game_state.type === "Ended") {
|
||||
endGame(result.game_state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,26 +110,13 @@ export function Game(props: {
|
|||
logDispatch(<div><em>{word} was added to dictionary.</em></div>);
|
||||
}
|
||||
|
||||
function runAI() {
|
||||
const result: TurnAdvanceResult = props.wasm.advance_turn();
|
||||
if(result.type == "AIMove"){
|
||||
handlePlayerAction(result.action, props.settings.aiName);
|
||||
} else {
|
||||
// this would be quite surprising
|
||||
console.error({result});
|
||||
}
|
||||
|
||||
setTurnCount(turnCount + 1);
|
||||
}
|
||||
|
||||
|
||||
const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []);
|
||||
const [logInfo, logDispatch] = useReducer(addLogInfo, []);
|
||||
|
||||
const [turnCount, setTurnCount] = useState<number>(1);
|
||||
const playerAndScores: PlayerAndScore[] = useMemo(() => {
|
||||
return props.wasm.get_scores();
|
||||
}, [turnCount]);
|
||||
}, [turnCount, isGameOver]);
|
||||
|
||||
const [boardLetters, setBoardLetters] = useState<HighlightableLetterData[]>(() => {
|
||||
const newLetterData = [] as HighlightableLetterData[];
|
||||
|
@ -156,18 +148,6 @@ export function Game(props: {
|
|||
return props.wasm.get_current_player();
|
||||
}, [turnCount]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
logDispatch(<h4>Turn {turnCount}</h4>);
|
||||
logDispatch(<div>{playerTurnName}'s turn</div>)
|
||||
setConfirmedScorePoints(-1);
|
||||
trayDispatch({action: TileDispatchActionType.RETRIEVE});
|
||||
if(playerTurnName != props.settings.playerName) {
|
||||
runAI();
|
||||
}
|
||||
|
||||
}, [turnCount]);
|
||||
|
||||
const logDivRef = useRef(null);
|
||||
|
||||
const [isTileExchangeOpen, setIsTileExchangeOpen] = useState<boolean>(false);
|
||||
|
@ -181,7 +161,7 @@ export function Game(props: {
|
|||
|
||||
const remainingTiles = useMemo(() => {
|
||||
return props.wasm.get_remaining_tiles();
|
||||
}, [turnCount]);
|
||||
}, [turnCount, isGameOver]);
|
||||
|
||||
const remainingAITiles = useMemo(() => {
|
||||
let result = props.wasm.get_player_tile_count(props.settings.aiName) as MyResult<number | String>;
|
||||
|
@ -192,7 +172,7 @@ export function Game(props: {
|
|||
return -1;
|
||||
}
|
||||
|
||||
}, [turnCount]);
|
||||
}, [turnCount, isGameOver]);
|
||||
|
||||
function handlePlayerAction(action: TurnAction, playerName: string) {
|
||||
|
||||
|
@ -211,6 +191,110 @@ export function Game(props: {
|
|||
}
|
||||
}
|
||||
|
||||
function endGame(state: GameState) {
|
||||
|
||||
if(state.type != "InProgress") {
|
||||
|
||||
setGameOver(true);
|
||||
logDispatch(<h4>Scoring</h4>);
|
||||
|
||||
const scores = props.wasm.get_scores() as PlayerAndScore[];
|
||||
let pointsBonus = 0;
|
||||
for(const playerAndScore of scores) {
|
||||
const name = playerAndScore.name;
|
||||
|
||||
if(name == state.finisher) {
|
||||
// we'll do the finisher last
|
||||
continue
|
||||
}
|
||||
|
||||
const letters = state.remaining_tiles.get(name);
|
||||
if(letters.length == 0) {
|
||||
logDispatch(<div>{name} has no remaining tiles.</div>);
|
||||
} else {
|
||||
let pointsLost = 0;
|
||||
let letterListStr = '';
|
||||
for(let i=0; i<letters.length; i++) {
|
||||
const letter = letters[i];
|
||||
const letterText = letter.is_blank ? 'a blank' : letter.text;
|
||||
pointsLost += letter.points;
|
||||
|
||||
letterListStr += letterText;
|
||||
|
||||
// we're doing a list of 3 or more so add commas
|
||||
if(letters.length > 2) {
|
||||
if(i == letters.length - 2) {
|
||||
letterListStr += ', and ';
|
||||
} else if (i < letters.length - 2) {
|
||||
letterListStr += ', ';
|
||||
}
|
||||
} else if (i == 0 && letters.length == 2){
|
||||
// list of 2
|
||||
letterListStr += ' and ';
|
||||
}
|
||||
|
||||
}
|
||||
logDispatch(<div>{name} penalized {pointsLost} points for not using {letterListStr}.</div>);
|
||||
pointsBonus += pointsLost;
|
||||
}
|
||||
}
|
||||
|
||||
if(state.finisher != null) {
|
||||
logDispatch(<div>{state.finisher} receives {pointsBonus} bonus for completing first.</div>);
|
||||
}
|
||||
|
||||
const highestScore = scores
|
||||
.map((score) => score.score)
|
||||
.sort((a, b) => b - a)
|
||||
.at(0);
|
||||
|
||||
const playersAtHighest = scores.filter((score) => score.score == highestScore);
|
||||
let endGameMsg: string = '';
|
||||
|
||||
if(playersAtHighest.length > 1 && state.finisher == null) {
|
||||
endGameMsg = "Tie game!";
|
||||
} else if (playersAtHighest.length > 1 && state.finisher != null) {
|
||||
// if there's a tie then the finisher gets the win
|
||||
endGameMsg = `${playersAtHighest[0].name} won by finishing first!`;
|
||||
}
|
||||
else {
|
||||
endGameMsg = `${playersAtHighest[0].name} won!`;
|
||||
}
|
||||
logDispatch(<h4>Game over - {endGameMsg}</h4>);
|
||||
} else {
|
||||
// what are we doing in this function?!
|
||||
console.error("endGame was called despite the state being InProgress!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function runAI() {
|
||||
const result: MyResult<TurnAdvanceResult> = props.wasm.advance_turn();
|
||||
if(result.response_type === "OK" && result.value.type == "AIMove") {
|
||||
handlePlayerAction(result.value.action, props.settings.aiName);
|
||||
if(result.game_state.type === "Ended") {
|
||||
endGame(result.game_state);
|
||||
}
|
||||
|
||||
} else {
|
||||
// this would be quite surprising
|
||||
console.error({result});
|
||||
}
|
||||
|
||||
setTurnCount(turnCount + 1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
logDispatch(<h4>Turn {turnCount}</h4>);
|
||||
logDispatch(<div>{playerTurnName}'s turn</div>)
|
||||
setConfirmedScorePoints(-1);
|
||||
trayDispatch({action: TileDispatchActionType.RETRIEVE});
|
||||
if(playerTurnName != props.settings.playerName && !isGameOver) {
|
||||
runAI();
|
||||
}
|
||||
|
||||
}, [turnCount]);
|
||||
|
||||
|
||||
return <>
|
||||
<TileExchangeModal
|
||||
|
@ -236,7 +320,9 @@ export function Game(props: {
|
|||
<div>
|
||||
{props.settings.aiName} has {remainingAITiles} tiles
|
||||
</div>
|
||||
<button onClick={() => {
|
||||
<button
|
||||
disabled={remainingTiles == 0}
|
||||
onClick={() => {
|
||||
trayDispatch({action: TileDispatchActionType.RETURN}); // want all tiles back on tray for tile exchange
|
||||
setIsTileExchangeOpen(true);
|
||||
}}>Open Tile Exchange</button>
|
||||
|
@ -289,6 +375,10 @@ export function Game(props: {
|
|||
setTurnCount(turnCount + 1);
|
||||
}
|
||||
|
||||
if (result.game_state.type === "Ended") {
|
||||
endGame(result.game_state);
|
||||
}
|
||||
|
||||
setConfirmedScorePoints(total_points);
|
||||
}
|
||||
|
||||
|
@ -299,9 +389,13 @@ export function Game(props: {
|
|||
trayDispatch({action: TileDispatchActionType.RETURN});
|
||||
}}>Return Tiles</button>
|
||||
<button className="pass" onClick={() => {
|
||||
props.wasm.skip_turn();
|
||||
const result = props.wasm.skip_turn() as MyResult<string>;
|
||||
handlePlayerAction({type: "Pass"}, props.settings.playerName);
|
||||
setTurnCount(turnCount + 1);
|
||||
|
||||
if(result.game_state.type === "Ended") {
|
||||
endGame(result.game_state);
|
||||
}
|
||||
}}>Pass</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -96,8 +96,10 @@ function TilesExchangedTray(props: {
|
|||
}
|
||||
}
|
||||
|
||||
return <div className="tray">
|
||||
{divContent}
|
||||
return <div className="tray-container">
|
||||
<div className="tray">
|
||||
{divContent}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
}
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
@board-length: 15;
|
||||
@tile-star-size: 45px;
|
||||
|
||||
.tray-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
width: @board-length*@tile-width;
|
||||
|
||||
.tray {
|
||||
.tray { // Don't put under tray-row as this also gets used in tile exchange
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, @tile-width);
|
||||
grid-gap: 5px;
|
||||
|
@ -16,7 +11,12 @@
|
|||
width: fit-content;
|
||||
background-color: #bbb59d;
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tray-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
width: @board-length*@tile-width;
|
||||
|
||||
.player-controls {
|
||||
display: grid;
|
||||
|
|
Loading…
Reference in a new issue