Skip to content

Commit

Permalink
Merge pull request #173 from TruncateGame/feat/lookups
Browse files Browse the repository at this point in the history
Add dictionary lookups in-game
  • Loading branch information
bglw authored Feb 4, 2024
2 parents 0a4eeb5 + 1cbd114 commit ef8206e
Show file tree
Hide file tree
Showing 15 changed files with 643 additions and 172 deletions.
16 changes: 12 additions & 4 deletions truncate_client/img/tile_order
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,18 @@ RESIGN_BUTTON_NW
RESIGN_BUTTON_NE
RESIGN_BUTTON_SW
RESIGN_BUTTON_SE
COLLAPSE_BUTTON_NW
COLLAPSE_BUTTON_NE
COLLAPSE_BUTTON_SW
COLLAPSE_BUTTON_SE
TRI_EAST_BUTTON_NW
TRI_EAST_BUTTON_NE
TRI_EAST_BUTTON_SW
TRI_EAST_BUTTON_SE
TRI_NORTH_BUTTON_NW
TRI_NORTH_BUTTON_NE
TRI_NORTH_BUTTON_SW
TRI_NORTH_BUTTON_SE
TRI_SOUTH_BUTTON_NW
TRI_SOUTH_BUTTON_NE
TRI_SOUTH_BUTTON_SW
TRI_SOUTH_BUTTON_SE
TOWN_BUTTON_NW
TOWN_BUTTON_NE
TOWN_BUTTON_SW
Expand Down
Binary file modified truncate_client/img/truncate.aseprite
Binary file not shown.
Binary file modified truncate_client/img/truncate_packed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion truncate_client/src/app_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,15 @@ pub fn handle_server_msg(outer: &mut OuterApplication, ui: &mut egui::Ui, curren
GameMessage::SupplyDefinitions(definitions) => {
match &mut outer.game_status {
GameStatus::SinglePlayer(game) => {
game.hydrate_meanings(definitions);
game.hydrate_meanings(definitions.clone());
if let Some(dict_ui) = &mut game.active_game.dictionary_ui {
dict_ui.load_definitions(definitions);
}
}
GameStatus::Active(active_game) => {
if let Some(dict_ui) = &mut active_game.dictionary_ui {
dict_ui.load_definitions(definitions);
}
}
_ => { /* Soft unreachable */ }
}
Expand Down
119 changes: 72 additions & 47 deletions truncate_client/src/lil_bits/battle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ use crate::utils::{

pub struct BattleUI<'a> {
battle: &'a BattleReport,
show_headline: bool,
}

impl<'a> BattleUI<'a> {
pub fn new(battle: &'a BattleReport) -> Self {
Self { battle }
pub fn new(battle: &'a BattleReport, show_headline: bool) -> Self {
Self {
battle,
show_headline,
}
}
}

Expand Down Expand Up @@ -250,56 +254,73 @@ impl<'a> BattleUI<'a> {

let mut battle_rect = Rect::NOTHING;

ui.allocate_ui_with_layout(
vec2(ui.available_size_before_wrap().x, 0.0),
Layout::left_to_right(Align::Center).with_main_wrap(true),
|ui| {
let words =
self.get_galleys(&self.battle.attackers, render_transparent, ui, &aesthetics);
battle_rect = battle_rect.union(self.paint_galleys(words, ui, false).rect);
},
);
ui.add_space(5.0);
if self.show_headline {
if !self.battle.attackers.is_empty() {
ui.allocate_ui_with_layout(
vec2(ui.available_size_before_wrap().x, 0.0),
Layout::left_to_right(Align::Center).with_main_wrap(true),
|ui| {
let words = self.get_galleys(
&self.battle.attackers,
render_transparent,
ui,
&aesthetics,
);
battle_rect = battle_rect.union(self.paint_galleys(words, ui, false).rect);
},
);
ui.add_space(5.0);
}

if !self.battle.attackers.is_empty() && !self.battle.defenders.is_empty() {
let (msg, _) = match self.battle.outcome {
truncate_core::judge::Outcome::AttackerWins(_) => {
("won an attack against", aesthetics.theme.word_valid)
}
truncate_core::judge::Outcome::DefenderWins => {
("failed an attack against", aesthetics.theme.word_invalid)
}
};

let galley = ui.painter().layout_no_wrap(
msg.to_string(),
FontId::new(
aesthetics.theme.letter_size * 0.3,
egui::FontFamily::Name("Truncate-Heavy".into()),
),
if render_transparent {
Color32::TRANSPARENT
} else {
aesthetics.theme.text
},
);
battle_rect = battle_rect.union(self.paint_galleys(vec![galley], ui, false).rect);
ui.add_space(5.0);
}

let (msg, _) = match self.battle.outcome {
truncate_core::judge::Outcome::AttackerWins(_) => {
("won an attack against", aesthetics.theme.word_valid)
if !self.battle.defenders.is_empty() {
ui.allocate_ui_with_layout(
vec2(ui.available_size_before_wrap().x, 0.0),
Layout::left_to_right(Align::Center).with_main_wrap(true),
|ui| {
let words = self.get_galleys(
&self.battle.defenders,
render_transparent,
ui,
&aesthetics,
);
battle_rect = battle_rect.union(self.paint_galleys(words, ui, false).rect);
},
);
}
truncate_core::judge::Outcome::DefenderWins => {
("failed an attack against", aesthetics.theme.word_invalid)

if !active {
return battle_rect;
}
};
let galley = ui.painter().layout_no_wrap(
msg.to_string(),
FontId::new(
aesthetics.theme.letter_size * 0.3,
egui::FontFamily::Name("Truncate-Heavy".into()),
),
if render_transparent {
Color32::TRANSPARENT
} else {
aesthetics.theme.text
},
);
battle_rect = battle_rect.union(self.paint_galleys(vec![galley], ui, false).rect);
ui.add_space(5.0);

ui.allocate_ui_with_layout(
vec2(ui.available_size_before_wrap().x, 0.0),
Layout::left_to_right(Align::Center).with_main_wrap(true),
|ui| {
let words =
self.get_galleys(&self.battle.defenders, render_transparent, ui, &aesthetics);
battle_rect = battle_rect.union(self.paint_galleys(words, ui, false).rect);
},
);

if !active {
return battle_rect;
ui.add_space(8.0);
}

ui.add_space(8.0);

let definition_space = ui.horizontal(|ui| {
ui.add_space(12.0);
ui.with_layout(Layout::top_down(Align::Min), |ui| {
Expand All @@ -323,7 +344,11 @@ impl<'a> BattleUI<'a> {

match (word.valid, &word.meanings) {
(Some(true), Some(meanings)) if !meanings.is_empty() => TextHelper::light(
&format!("{}: {}", meanings[0].pos, meanings[0].defs[0]),
&if meanings[0].pos.is_empty() {
format!("{}", meanings[0].defs[0])
} else {
format!("{}: {}", meanings[0].pos, meanings[0].defs[0])
},
24.0,
Some(ui.available_width()),
ui,
Expand Down
192 changes: 192 additions & 0 deletions truncate_client/src/lil_bits/dictionary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use eframe::egui::{self, Id, Layout, Sense, WidgetText};
use epaint::{
emath::{Align, Align2, NumExt},
pos2, vec2, Color32, Rect, Stroke, TextShape, TextureHandle, Vec2,
};
use instant::Duration;
use std::{collections::HashMap, f32};
use truncate_core::{
judge::Outcome,
messages::PlayerMessage,
reporting::{BattleReport, BattleWord, WordMeaning},
};

use crate::utils::{
depot::{AestheticDepot, TruncateDepot},
game_evals::get_main_dict,
text::TextHelper,
Lighten, Theme,
};

use super::BattleUI;

#[derive(Clone)]
pub struct DictionaryUI {
current_word: String,
is_valid: bool,
definitions: HashMap<String, Option<Vec<WordMeaning>>>,
}

impl DictionaryUI {
pub fn new() -> Self {
Self {
current_word: String::new(),
is_valid: false,
definitions: HashMap::new(),
}
}

pub fn load_definitions(&mut self, definitions: Vec<(String, Option<Vec<WordMeaning>>)>) {
for (word, def) in definitions {
self.definitions.insert(word, def);
}
}

pub fn render(
&mut self,
ui: &mut egui::Ui,
depot: &mut TruncateDepot,
) -> Option<PlayerMessage> {
let mut msg = None;

let input_fz = 20.0;

let desired_input_width = ui.available_width().min(500.0);
let desired_input_height = input_fz * 2.0;

ui.add_space(20.0);
let inset = (ui.available_width() - desired_input_width) / 2.0;
let (input_band, _) = ui.allocate_exact_size(
vec2(ui.available_width(), desired_input_height),
Sense::hover(),
);
let input_inner = input_band.shrink2(vec2(inset, 0.0));
let mut input_ui = ui.child_ui(input_inner, Layout::top_down(Align::LEFT));

ui.painter().rect_filled(
input_inner.expand(4.0),
4.0,
depot.aesthetics.theme.text.gamma_multiply(0.8),
);
ui.painter().rect_stroke(
input_inner.expand(4.0),
4.0,
Stroke::new(2.0, Color32::WHITE.gamma_multiply(0.5)),
);

let input = egui::TextEdit::singleline(&mut self.current_word)
.desired_width(f32::INFINITY)
.frame(false)
.margin(egui::vec2(4.0, 2.0))
.min_size(vec2(0.0, desired_input_height))
.text_color(if self.is_valid {
depot.aesthetics.theme.word_valid.lighten()
} else {
depot.aesthetics.theme.word_invalid.lighten().lighten()
})
.horizontal_align(Align::Center)
.vertical_align(Align::Center)
.font(egui::FontId::new(
input_fz,
egui::FontFamily::Name("Truncate-Heavy".into()),
))
.show(&mut input_ui);

if input.response.changed() {
self.current_word = self.current_word.to_ascii_lowercase();

let dict_lock = get_main_dict();
let dict = dict_lock.as_ref().unwrap();

self.is_valid = dict.contains_key(&self.current_word);

if self.is_valid && !self.definitions.contains_key(&self.current_word) {
msg = Some(PlayerMessage::RequestDefinitions(vec![self
.current_word
.clone()]));
}
}

if !self.current_word.is_empty() {
let meanings = if self.is_valid {
let loading_meaning = if !self.definitions.contains_key(&self.current_word) {
Some(vec![WordMeaning {
pos: "".to_string(),
defs: vec!["Loading definitions...".to_string()],
}])
} else {
None
};

loading_meaning.or(self.definitions.get(&self.current_word).cloned().flatten())
} else {
Some(vec![WordMeaning {
pos: "".to_string(),
defs: vec!["Invalid word".to_string()],
}])
};

let report = BattleReport {
battle_number: None,
attackers: vec![],
defenders: vec![BattleWord {
original_word: self.current_word.clone(),
resolved_word: self.current_word.clone(),
meanings,
valid: Some(self.is_valid),
}],
outcome: Outcome::DefenderWins,
};

let desired_battle_width = ui.available_width().min(500.0);

ui.add_space(20.0);

let inset = (ui.available_width() - desired_battle_width) / 2.0;
let (battle_band, _) = ui.allocate_exact_size(
vec2(ui.available_width(), ui.available_height()),
Sense::hover(),
);
let battle_inner = battle_band.shrink2(vec2(inset, 0.0));
let mut battle_ui = ui.child_ui(battle_inner, Layout::top_down(Align::LEFT));

BattleUI::new(&report, false).render(&mut battle_ui, depot);
} else {
let text = TextHelper::heavy("Search", 20.0, None, ui);
text.paint_within(
input.response.rect,
Align2::CENTER_CENTER,
Color32::WHITE.gamma_multiply(0.8),
ui,
);

ui.add_space(20.0);

let (dialog_rect, _) = crate::utils::tex::paint_dialog_background(
false,
false,
true,
vec2(input.response.rect.width(), 200.0),
depot.aesthetics.theme.water.lighten().lighten(),
&depot.aesthetics.map_texture,
ui,
);

let dialog_text = TextHelper::light(
"Use the input above to check whether a word exists in Truncate's dictionary",
32.0,
Some(dialog_rect.shrink(16.0).width()),
ui,
);

dialog_text.paint_within(
dialog_rect,
Align2::CENTER_CENTER,
depot.aesthetics.theme.text,
ui,
);
}

msg
}
}
4 changes: 3 additions & 1 deletion truncate_client/src/lil_bits/hand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,11 @@ impl<'a> HandUI<'a> {
self.hand.len(),
1,
0.5..1.3,
(2, 0),
(0, 0),
);

depot.ui_state.hand_height_last_frame = theme.grid_size;

let old_theme = aesthetics.theme.clone();
aesthetics.theme = theme;

Expand Down
Loading

0 comments on commit ef8206e

Please sign in to comment.