1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#![warn(missing_docs)]
use super::charview::screen_character::ScreenCharacter;
pub use super::charview::{CharChunkMap, ViewportLocation};
pub use tui::style::{Color, Modifier as Font};
pub use crossterm::event::{
Event as GameEvent, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MouseEvent,
};
pub use super::{Message, SCREEN_HEIGHT, SCREEN_WIDTH};
pub use crate::styled_characters::{Style as GameStyle, StyledCharacter};
/// This is an enum to make it easy to match on events.
#[derive(Debug, PartialOrd, Clone, PartialEq, Eq, Hash)]
pub enum SimpleEvent {
/// This happens when the user holds Control
WithControl(KeyCode),
/// This happens when the user holds Alt
WithAlt(KeyCode),
/// This happens when the user holds Control AND Alt
WithControlAlt(KeyCode),
/// This happens when the user just presses a key
Just(KeyCode),
/// This is when an event is more complicated than a keypress.
ComplexEvent(GameEvent),
}
impl From<SimpleEvent> for GameEvent {
fn from(event: SimpleEvent) -> GameEvent {
let (c, modifiers) = match event {
SimpleEvent::WithControl(c) => (c, KeyModifiers::CONTROL),
SimpleEvent::WithAlt(c) => (c, KeyModifiers::ALT),
SimpleEvent::WithControlAlt(c) => (c, KeyModifiers::CONTROL | KeyModifiers::ALT),
SimpleEvent::Just(c) => (c, KeyModifiers::NONE),
SimpleEvent::ComplexEvent(e) => return e,
};
GameEvent::Key(KeyEvent::new(c, modifiers))
}
}
impl From<GameEvent> for SimpleEvent {
fn from(event: GameEvent) -> SimpleEvent {
const CONTROL_ALT: KeyModifiers = KeyModifiers::CONTROL.union(KeyModifiers::ALT);
match event {
GameEvent::Key(KeyEvent {
code, modifiers, ..
}) => match modifiers.intersection(KeyModifiers::CONTROL | KeyModifiers::ALT) {
CONTROL_ALT => SimpleEvent::WithControlAlt(code),
KeyModifiers::CONTROL => SimpleEvent::WithControl(code),
KeyModifiers::ALT => SimpleEvent::WithAlt(code),
KeyModifiers::NONE => SimpleEvent::Just(code),
_ => unreachable!(),
},
e => return SimpleEvent::ComplexEvent(e),
}
}
}
/// The Game struct is passed to all of the Controller's event methods,
/// to allow the implementor to view and modify the state of the game.
pub struct Game<'a> {
/// This determines whether the game will end soon.
pub(super) should_end: bool,
/// If Some, a message will be shown at the bottom of the screen.
pub(super) message: Option<Message>,
/// The place in the viewport that is currently the top-left pixel.
pub(super) viewport: ViewportLocation,
/// The chunkmap of the display.
pub(super) chunks: &'a mut CharChunkMap,
}
impl<'a> Game<'a> {
/// Create a game, given a [`CharChunkMap`].
pub fn new(chunks: &mut CharChunkMap) -> Game {
Game {
should_end: false,
message: None,
viewport: ViewportLocation { x: 0, y: 0 },
chunks,
}
}
/// Get the screen size, in the form of (x, (y1, y2))
///
/// `x` is the width of the screen. `y1` is the height of
/// the game area; and `y2` is the height of the question area.
pub fn screen_size(&self) -> (u16, (u16, u16)) {
match self.message {
Some(ref m) => {
let rows: u16 = (m.text.matches('\n').count() + 3).try_into().unwrap();
(SCREEN_WIDTH, ((SCREEN_HEIGHT - rows), rows))
}
None => (SCREEN_WIDTH, (SCREEN_HEIGHT, 0)),
}
}
/// Obtain the current message being shown.
/// `None` if no message is showing.
pub fn get_message(&self) -> &Option<Message> {
&self.message
}
/// Set a new message to be shown; or if `message` is None,
/// remove any current message.
pub fn set_message(&mut self, message: Option<Message>) {
self.message = message;
}
/// Returns `true` if the game is about to end. This
/// is only `true` if `end_game` has been called.
pub fn game_will_end(&self) -> bool {
self.should_end
}
/// Ends the game. This event handler will never be called again.
/// Other event handlers may be called at most once, before the
/// game is ended and the `run_game` function ends.
pub fn end_game(&mut self) {
self.should_end = true;
}
/// Return the character at the given (x, y) coordinates.
///
/// If the return is `None`, nothing is at those coordinates.
/// If the return is `Some`, the [`StyledCharacter`] returned
/// is the character at those coordinates..
pub fn get_screen_char(&self, x: i32, y: i32) -> Option<StyledCharacter> {
self.chunks.get(x, y).map(|x| StyledCharacter::from(*x))
}
/// Place the character at the given (x, y) coordinates.
///
/// If `character` is `None`, remove anything at those coordinates.
/// If `character` is `Some`, insert [`StyledCharacter`] at those coordinates.
pub fn set_screen_char(&mut self, x: i32, y: i32, character: Option<StyledCharacter>) {
match character {
Some(c) => self.chunks.insert(x, y, ScreenCharacter::from(c)),
None => {
self.chunks.remove(x, y);
}
}
}
/// This function takes a mutable reference to a chunkmap and
/// swaps it out for another one. This allows you to do things
/// like keep multiple maps at once; or do efficient re-builds of
/// the screen.
///
/// ```rust
/// use termgame::{CharChunkMap, Game};
/// let mut chunkmap1 = CharChunkMap::new();
/// let mut chunkmap2 = CharChunkMap::new();
/// chunkmap2.insert(1, 1, 'a'.into());
/// let mut game = Game::new(&mut chunkmap1);
/// game.swap_chunkmap(&mut chunkmap2);
/// ````
pub fn swap_chunkmap(&mut self, chunkmap: &mut CharChunkMap) {
std::mem::swap(self.chunks, chunkmap);
}
/// Get the current [`ViewportLocation`]. This tells you the
/// top-left coordinate currently in view.
pub fn get_viewport(&self) -> ViewportLocation {
self.viewport
}
/// This sets the viewport (i.e. the top-left coordniate currently in view)
/// to the provided [`ViewportLocation`].
pub fn set_viewport(&mut self, viewport: ViewportLocation) {
self.viewport = viewport;
}
}