pub struct Region<'a> { /* private fields */ }Expand description
A temporary view of a logical region of a Page that allows
writing text
The region is logical in the sense that it may be partly or fully outside of the actual boundaries of the page, or of any region it was derived from. All data written is only written if it is within the clip. The clip is the smallest of the page, the region and all the parent regions. However on writing, the logical write position is advanced as if the characters were written in any case.
This acts like a mini terminal in that it remembers its current
hfb colour and write position, and lets you change them, and
write text to the ‘terminal’. Text will character-wrap at the
region’s logical right boundary back to the region’s logical left
boundary. This also implements the Write trait so it can be
used for formatted output with write!(region, ...).
However note that the region doesn’t scroll like a real terminal when you reach the bottom. (It can’t since the page doesn’t necessarily store the whole region’s contents.)
Any sequences of the form \0ZZZ written to the region represent
an HFB colour and change the current HFB colour. Generate one of
these sequences using a string escape sequence (e.g. "\0099" to
set default fg/bg), or else by using the HFB type which allows
encoding any 16-bit HFB value.
Implementations§
Source§impl Region<'_>
impl Region<'_>
Sourcepub fn full(&mut self) -> Region<'_> ⓘ
pub fn full(&mut self) -> Region<'_> ⓘ
Generate a child region the same size as this one. This is like a clone of the region except that it borrows from the parent region whilst it exists.
Examples found in repository?
218 fn draw_mini(&mut self, cx: CX![]) {
219 if let Some(mut full) = self.mini_tile.full(cx) {
220 full.clear_all_99();
221 let sx = full.sx() * 2;
222 let sy = full.sy() * 4;
223 let font_sx = self.font.sx as i32;
224 let font_sy = self.font.sy as i32;
225 let oy = sy.saturating_sub(font_sy) / 2;
226 let mut ox = (sx - font_sx) / 2;
227 let mut index = self.index;
228 while ox > 0 && index > 0 {
229 ox -= font_sx;
230 index -= 1;
231 }
232 while ox < sx {
233 draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234 ox += font_sx;
235 index += 1;
236 }
237 }
238 }Sourcepub fn region(&mut self, y: i32, x: i32, sy: i32, sx: i32) -> Region<'_> ⓘ
pub fn region(&mut self, y: i32, x: i32, sy: i32, sx: i32) -> Region<'_> ⓘ
Generate a child region that may be any size, inside or outside this region. When drawn to, only the part of the child region that overlaps this region (and all its parent regions) will be affected.
Examples found in repository?
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10 let mut r = r.region(y, x, CARD_SY, CARD_SX);
11 r.hfb(card.hfb).clear_all();
12 r.at(0, 2);
13 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14 r.at(CARD_SY - 1, 2);
15 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20 let mut r = r.region(y, x, CARD_SY, CARD_SX);
21 r.hfb(27);
22 for y in 0..CARD_SY {
23 r.at(y, 0).text("||||||");
24 }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29 let mut r = r.region(y, x, CARD_SY, CARD_SX);
30 r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35 let mut r = r.region(y, x, CARD_SY, CARD_SX);
36 r.hfb(70);
37 for y in 0..CARD_SY {
38 r.at(y, 0).text("::::::");
39 }
40}More examples
132 fn draw_param(&mut self, cx: CX![]) {
133 if let Some(mut r) = self.param_tile.full(cx) {
134 r.hfb(172).clear_all();
135
136 // Use sub-region to clip the filename if it's too long
137 r.region(0, 0, 1, 39)
138 .hfb(172)
139 .text("File: ")
140 .text(&self.font.path.to_string_lossy());
141
142 let count = self.font.index_limit();
143 let index = self.index;
144
145 let state = if let Some(ref err) = self.save_error {
146 err
147 } else if self.unsaved {
148 "UNSAVED"
149 } else {
150 ""
151 };
152 let cp = self.font.chars[index].cp;
153 let ch = match cp {
154 32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155 _ => '?',
156 };
157 let sx = self.font.sx;
158 let sy = self.font.sy;
159
160 r.at(1, 0);
161 let _ = writeln!(r, "Size: {sx} x {sy}, {count} glyphs");
162 let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163 let _ = writeln!(r, "Mode: {}", self.mode.caps());
164 let _ = writeln!(r, "State: {}", state);
165 }
166 }
167
168 fn draw_cells(&mut self, cx: CX![]) {
169 if let Some(mut full) = self.cells_tile.full(cx) {
170 let sx = full.sx();
171 full.clear_all_99();
172
173 let font_sx = self.font.sx;
174 let font_sy = self.font.sy;
175 let cell_wid = font_sx * 2;
176 let r = full.region(
177 0,
178 (sx - cell_wid as i32) / 2,
179 font_sy as i32,
180 cell_wid as i32,
181 );
182 draw_cell(
183 &self.font,
184 r,
185 self.index,
186 171,
187 7,
188 Some((self.cy, self.cx, self.mode)),
189 );
190 let max = self.font.index_limit();
191 for i in 1..=self.index {
192 let r = full.region(
193 0,
194 (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195 font_sy as i32,
196 cell_wid as i32,
197 );
198 if !r.is_visible() {
199 break;
200 }
201 draw_cell(&self.font, r, self.index - i, 70, 6, None);
202 }
203 for i in 1..(max - self.index) {
204 let r = full.region(
205 0,
206 (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207 font_sy as i32,
208 cell_wid as i32,
209 );
210 if !r.is_visible() {
211 break;
212 }
213 draw_cell(&self.font, r, self.index + i, 70, 6, None);
214 }
215 }
216 }Sourcepub fn region_inplace(&mut self, y: i32, x: i32, sy: i32, sx: i32)
pub fn region_inplace(&mut self, y: i32, x: i32, sy: i32, sx: i32)
Replace this region with a child region that may be any size, inside or outside the old region. When drawn to, only the part of the new region that overlaps the old region (and all its parent regions) will be affected.
Sourcepub fn sy(&self) -> i32
pub fn sy(&self) -> i32
Get the region size-Y, i.e. rows
Examples found in repository?
218 fn draw_mini(&mut self, cx: CX![]) {
219 if let Some(mut full) = self.mini_tile.full(cx) {
220 full.clear_all_99();
221 let sx = full.sx() * 2;
222 let sy = full.sy() * 4;
223 let font_sx = self.font.sx as i32;
224 let font_sy = self.font.sy as i32;
225 let oy = sy.saturating_sub(font_sy) / 2;
226 let mut ox = (sx - font_sx) / 2;
227 let mut index = self.index;
228 while ox > 0 && index > 0 {
229 ox -= font_sx;
230 index -= 1;
231 }
232 while ox < sx {
233 draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234 ox += font_sx;
235 index += 1;
236 }
237 }
238 }More examples
172 fn draw_scores(&mut self, cx: CX![]) {
173 let mut score = self.score;
174 let mut last_score = self.last_score;
175 if let Some(mut r) = self.scores_tile.full(cx) {
176 r.hfb(176).clear_all();
177 r.hfb(7).text("SCORES");
178 let mut y = 1;
179 let last_y = r.sy() - 1;
180 let mut draw = move |hfb, sc| {
181 if y > 0 {
182 r.at(y, 0).hfb(hfb);
183 write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184 y += 1;
185 }
186 };
187
188 for &s0 in &self.scores {
189 if let Some(curr) = score
190 && (curr < s0 || y == last_y)
191 {
192 draw(7, curr);
193 score = None;
194 }
195 if Some(s0) == last_score {
196 draw(173, s0);
197 last_score = None;
198 } else {
199 draw(176, s0);
200 }
201 }
202 if let Some(s1) = score {
203 draw(173, s1);
204 }
205 }
206 }
207
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }
238
239 fn draw_instructions(mut r: Region<'_>) {
240 const TEXT: &str = r"
241
242 Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243 corresponding column; [+]/[=] or [-] Increase or decrease
244 number of selected cards; [1] to [9] If the same digit as the
245 selecting keypress, move selected cards to top, if another
246 digit, move as many of selected cards as possible to that
247 column; [Space] Bring a new card down from the stock pile;
248 [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250 Rules: The aim is to move all the cards to the top area, the
251 spaces the the right of the stock pile. Each top pile must
252 consist of one suit only, stacked in order from Ace up to
253 King. The cards in the main area can be moved around:
254 sequences of one or more cards of the same suit may be moved
255 on top of a card with the next-higher number, of any suit.
256 The bulk of the cards are in the stock pile in the top-left.
257 Cards may be brought down from there using Space.
258
259 ";
260 let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261 let n_lines = lines.len();
262 let oy = (r.sy() - n_lines as i32) / 2;
263 for (i, line) in lines.iter().enumerate() {
264 r.at(i as i32 + oy, 7);
265 let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266 r.text(&line);
267 }
268 }
269
270 fn input(&mut self, cx: CX![], key: Key) {
271 match key {
272 Key::Ctrl('L') => self.redraw(cx),
273 Key::Ctrl('C') => stop!(cx),
274 Key::Pr('q' | 'Q') => {
275 if self.game.is_some() {
276 self.game = None;
277 self.score = None;
278 self.layout(cx);
279 } else {
280 stop!(cx);
281 }
282 }
283 Key::Pr('n' | 'N') => {
284 self.game = Some(Game::new(
285 cx.now(),
286 ret_some_to!([cx], game_finished() as (Duration)),
287 ));
288 self.last_score = None;
289 self.update_score(cx);
290 self.layout(cx);
291 }
292 Key::Pr('<') => {
293 self.adjust -= 1;
294 self.save_scores().expect("Failed to save scores");
295 self.layout(cx);
296 }
297 Key::Pr('>') => {
298 self.adjust += 1;
299 self.save_scores().expect("Failed to save scores");
300 self.layout(cx);
301 }
302 _ => {
303 if let Some(game) = &mut self.game {
304 game.input(cx, key);
305 }
306 }
307 }
308 }
309
310 fn update_score(&mut self, cx: CX![]) {
311 if let Some(game) = &mut self.game {
312 let dur = game.score(cx);
313 let score = dur.as_secs() as i32;
314 let to_wait = if Some(score) != self.score {
315 self.score = Some(score);
316 self.draw_scores(cx);
317
318 // Update 100ms after next change
319 1_000_000_000 - dur.subsec_nanos() + 100_000_000
320 } else {
321 1_000_000_000
322 };
323 after!(Duration::from_nanos(to_wait as u64), [cx], update_score());
324 }
325 }
326
327 fn game_finished(&mut self, cx: CX![], dur: Duration) {
328 let score = dur.as_secs() as i32;
329 self.scores.push(score);
330 self.scores.sort_unstable();
331 self.save_scores().expect("Failed to save scores");
332 self.last_score = Some(score);
333 self.score = None;
334 self.game = None;
335 self.layout(cx);
336 }
337
338 fn score_path() -> PathBuf {
339 let mut path = std::env::home_dir().unwrap_or_default();
340 path.push(".teeclub-scores");
341 path
342 }
343
344 fn load_scores(&mut self) -> Option<()> {
345 let data = std::fs::read_to_string(Self::score_path()).ok()?;
346 let mut it = data.split(char::is_whitespace);
347 while let Some(tok) = it.next() {
348 match tok {
349 "adjust:" => {
350 self.adjust = it.next()?.parse::<i32>().ok()?;
351 }
352 "scores[" => {
353 self.scores = Vec::new();
354 loop {
355 match it.next()? {
356 "]" => break,
357 v => self.scores.push(v.parse::<i32>().ok()?),
358 }
359 }
360 self.scores.sort_unstable();
361 }
362 "" => (),
363 _ => return None,
364 }
365 }
366 Some(())
367 }
368
369 fn save_scores(&mut self) -> std::io::Result<()> {
370 let mut out = Vec::new();
371 let _ = writeln!(out, "adjust: {}", self.adjust);
372 let _ = writeln!(out, "scores[");
373 for sc in self.scores.iter().take(200) {
374 let _ = writeln!(out, "{sc}");
375 }
376 let _ = writeln!(out, "]");
377 std::fs::write(Self::score_path(), out)
378 }
379}
380
381/// Game state
382#[derive(Eq, PartialEq)]
383struct State {
384 pile: [Hand; 9],
385 stack: [Hand; 9],
386}
387
388impl State {
389 fn pack(&self) -> Vec<u8> {
390 let mut out = Vec::new();
391 for p in &self.pile {
392 p.pack(&mut out);
393 }
394 for s in &self.stack {
395 s.pack(&mut out);
396 }
397 out
398 }
399
400 fn unpack(mut data: &[u8]) -> Self {
401 let mut pile: [Hand; 9] = Default::default();
402 let mut stack: [Hand; 9] = Default::default();
403 for p in &mut pile {
404 *p = Hand::unpack(&mut data);
405 }
406 for s in &mut stack {
407 *s = Hand::unpack(&mut data);
408 }
409 Self { pile, stack }
410 }
411}
412
413/// Game state and gameplay handling
414struct Game {
415 tile: Tile,
416 history: Vec<Vec<u8>>,
417 state: State,
418 select: Option<(usize, usize)>, // (index, count)
419 dur: Duration,
420 activity: Instant,
421 ret: Option<Ret<Duration>>, // Report score for game completion
422}
423
424impl Game {
425 fn new(start: Instant, ret: Ret<Duration>) -> Self {
426 let seed = std::time::SystemTime::UNIX_EPOCH
427 .elapsed()
428 .map(|d| d.as_nanos() as u64)
429 .unwrap_or(0);
430
431 let mut rand = Rand32::new(seed);
432 let mut pile: [Hand; 9] = Default::default();
433 let mut stack: [Hand; 9] = Default::default();
434
435 let p0 = &mut pile[0];
436 p0.add_deck();
437 p0.add_deck();
438 p0.shuffle(&mut rand);
439 p0.shuffle(&mut rand);
440
441 for _ in 0..5 {
442 for s in &mut stack {
443 if let Some(card) = p0.pick_last() {
444 s.add(card);
445 }
446 }
447 }
448
449 Self {
450 tile: Tile::default(),
451 history: Vec::new(),
452 state: State { pile, stack },
453 select: None,
454 dur: Duration::from_secs(0),
455 activity: start,
456 ret: Some(ret),
457 }
458 }
459
460 fn select(&mut self, core: &mut Core, i: usize) {
461 let s = &self.state.stack[i];
462 let len = s.len();
463 if len == 0 {
464 self.select = None;
465 } else {
466 let mut count = 1;
467 while count < len && s[len - count].inc == Some(s[len - count - 1]) {
468 count += 1;
469 }
470 self.select = Some((i, count));
471 }
472 self.draw(core);
473 }
474
475 fn sel_inc(&mut self, core: &mut Core) {
476 if let Some((i, count)) = self.select {
477 let s = &self.state.stack[i];
478 let len = s.len();
479 if count < len && s[len - count].inc == Some(s[len - count - 1]) {
480 self.select = Some((i, count + 1));
481 self.draw(core);
482 }
483 }
484 }
485
486 fn sel_dec(&mut self, core: &mut Core) {
487 if let Some(sel) = &mut self.select {
488 let i = sel.0;
489 let count = sel.1;
490 if count <= 1 {
491 self.select = None;
492 } else {
493 self.select = Some((i, count - 1));
494 }
495 self.draw(core);
496 }
497 }
498
499 /// Mark this state by saving it in history, available to undo
500 /// later
501 fn mark(&mut self) {
502 let v = self.state.pack();
503 if let Some(last) = self.history.last()
504 && *last == v
505 {
506 return;
507 }
508 self.history.push(v);
509 }
510
511 /// Restore most recent marked state and remove it from history
512 fn undo(&mut self, core: &mut Core) {
513 if let Some(data) = self.history.pop() {
514 self.state = State::unpack(&data);
515 self.select = None;
516 self.draw(core);
517 }
518 }
519
520 /// Move selected cards to the given stack or to top if the stack
521 /// is the selected stack
522 fn move_to(&mut self, core: &mut Core, to: usize) {
523 let Some((fr, cnt)) = self.select else {
524 return;
525 };
526 self.select = None;
527 self.mark();
528
529 let s = &mut self.state;
530 let Some(fr_last) = s.stack[fr].peek_last() else {
531 return;
532 };
533
534 if fr == to {
535 // Move to top
536 for pi in (1..9).rev() {
537 let do_move = if let Some(to_last) = s.pile[pi].peek_last() {
538 to_last.inc == Some(fr_last)
539 } else {
540 fr_last.num == 1
541 };
542 if do_move {
543 for _ in 0..cnt {
544 if let Some(card) = s.stack[fr].pick_last() {
545 s.pile[pi].add(card);
546 }
547 }
548 break;
549 }
550 }
551 if self.is_finished()
552 && let Some(ret) = self.ret.take()
553 {
554 ret!([ret], self.score(core));
555 }
556 } else {
557 // Move to another pile
558 let mut copy_cnt = cnt;
559 if let Some(to_last) = s.stack[to].peek_last() {
560 copy_cnt = copy_cnt.min(to_last.num.saturating_sub(fr_last.num) as usize);
561 if to_last.num != fr_last.num + copy_cnt as u8 {
562 copy_cnt = 0;
563 }
564 }
565
566 let mut tmp = Vec::new();
567 for _ in 0..copy_cnt {
568 tmp.push(s.stack[fr].pick_last().unwrap());
569 }
570 while let Some(card) = tmp.pop() {
571 s.stack[to].add(card);
572 }
573 }
574 self.draw(core);
575 }
576
577 /// Move card down from stock pile
578 fn move_down(&mut self, core: &mut Core) {
579 self.mark();
580 if let Some(card) = self.state.pile[0].pick_last() {
581 self.state.stack[0].add(card);
582 self.select = None;
583 self.draw(core);
584 }
585 }
586
587 fn is_finished(&mut self) -> bool {
588 for i in 1..9 {
589 if self.state.pile[i].len() != 13 {
590 return false;
591 }
592 }
593 true
594 }
595
596 /// Set the tile to use for drawing, and redraw
597 fn layout(&mut self, core: &mut Core, tile: Tile) {
598 self.tile = tile;
599 self.draw(core);
600 }
601
602 /// Draw the playing area
603 fn draw(&mut self, core: &mut Core) {
604 if let Some(mut r) = self.tile.full(core) {
605 r.clear_all_99();
606
607 for col in 0..9 {
608 let x = 1 + col as i32 * 8;
609
610 // Pile
611 if let Some(card) = self.state.pile[col].peek_last() {
612 if col == 0 {
613 // Facedown pile
614 draw::card_back(&mut r, 0, x);
615 } else {
616 draw::card(&mut r, 0, x, card);
617 }
618 } else {
619 draw::card_space(&mut r, 0, x);
620 }
621
622 // Index number
623 r.hfb(7)
624 .at(CARD_SY, x)
625 .hfb(70)
626 .char((49 + col as u8) as char);
627
628 // Stack
629 let y0 = CARD_SY + 1;
630 let y1 = r.sy();
631 let hand = &self.state.stack[col];
632 draw::card_space(&mut r, y0, x);
633
634 let selected = self
635 .select
636 .map(|(index, count)| if col == index { count } else { 0 })
637 .unwrap_or(0);
638 let len = hand.len();
639 let sel_i = len.saturating_sub(selected);
640 let space = y1 - y0 - 1;
641 let mut y = y0;
642 let mut i = 0;
643 if len as i32 + CARD_SY > space {
644 draw::card_dots(&mut r, y, x);
645 y += 1;
646 i = len - (space - CARD_SY) as usize;
647 }
648 while i < hand.len() {
649 let card = hand[i];
650 if i >= sel_i {
651 draw::card(&mut r, y + 1, x + 1, card);
652 } else {
653 draw::card(&mut r, y, x, card);
654 }
655 i += 1;
656 y += 1;
657 }
658 }
659 }
660 }Sourcepub fn sx(&self) -> i32
pub fn sx(&self) -> i32
Get the region size-X, i.e. columns
Examples found in repository?
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }More examples
168 fn draw_cells(&mut self, cx: CX![]) {
169 if let Some(mut full) = self.cells_tile.full(cx) {
170 let sx = full.sx();
171 full.clear_all_99();
172
173 let font_sx = self.font.sx;
174 let font_sy = self.font.sy;
175 let cell_wid = font_sx * 2;
176 let r = full.region(
177 0,
178 (sx - cell_wid as i32) / 2,
179 font_sy as i32,
180 cell_wid as i32,
181 );
182 draw_cell(
183 &self.font,
184 r,
185 self.index,
186 171,
187 7,
188 Some((self.cy, self.cx, self.mode)),
189 );
190 let max = self.font.index_limit();
191 for i in 1..=self.index {
192 let r = full.region(
193 0,
194 (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195 font_sy as i32,
196 cell_wid as i32,
197 );
198 if !r.is_visible() {
199 break;
200 }
201 draw_cell(&self.font, r, self.index - i, 70, 6, None);
202 }
203 for i in 1..(max - self.index) {
204 let r = full.region(
205 0,
206 (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207 font_sy as i32,
208 cell_wid as i32,
209 );
210 if !r.is_visible() {
211 break;
212 }
213 draw_cell(&self.font, r, self.index + i, 70, 6, None);
214 }
215 }
216 }
217
218 fn draw_mini(&mut self, cx: CX![]) {
219 if let Some(mut full) = self.mini_tile.full(cx) {
220 full.clear_all_99();
221 let sx = full.sx() * 2;
222 let sy = full.sy() * 4;
223 let font_sx = self.font.sx as i32;
224 let font_sy = self.font.sy as i32;
225 let oy = sy.saturating_sub(font_sy) / 2;
226 let mut ox = (sx - font_sx) / 2;
227 let mut index = self.index;
228 while ox > 0 && index > 0 {
229 ox -= font_sx;
230 index -= 1;
231 }
232 while ox < sx {
233 draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234 ox += font_sx;
235 index += 1;
236 }
237 }
238 }Sourcepub fn get_x(&self) -> i32
pub fn get_x(&self) -> i32
Get the current write column (X-position)
Examples found in repository?
97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }Sourcepub fn is_visible(&self) -> bool
pub fn is_visible(&self) -> bool
Check whether any part of this region is visible on the page
Examples found in repository?
168 fn draw_cells(&mut self, cx: CX![]) {
169 if let Some(mut full) = self.cells_tile.full(cx) {
170 let sx = full.sx();
171 full.clear_all_99();
172
173 let font_sx = self.font.sx;
174 let font_sy = self.font.sy;
175 let cell_wid = font_sx * 2;
176 let r = full.region(
177 0,
178 (sx - cell_wid as i32) / 2,
179 font_sy as i32,
180 cell_wid as i32,
181 );
182 draw_cell(
183 &self.font,
184 r,
185 self.index,
186 171,
187 7,
188 Some((self.cy, self.cx, self.mode)),
189 );
190 let max = self.font.index_limit();
191 for i in 1..=self.index {
192 let r = full.region(
193 0,
194 (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195 font_sy as i32,
196 cell_wid as i32,
197 );
198 if !r.is_visible() {
199 break;
200 }
201 draw_cell(&self.font, r, self.index - i, 70, 6, None);
202 }
203 for i in 1..(max - self.index) {
204 let r = full.region(
205 0,
206 (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207 font_sy as i32,
208 cell_wid as i32,
209 );
210 if !r.is_visible() {
211 break;
212 }
213 draw_cell(&self.font, r, self.index + i, 70, 6, None);
214 }
215 }
216 }Sourcepub fn hfb(&mut self, hfb: u16) -> &mut Self
pub fn hfb(&mut self, hfb: u16) -> &mut Self
Change the current HFB
Examples found in repository?
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10 let mut r = r.region(y, x, CARD_SY, CARD_SX);
11 r.hfb(card.hfb).clear_all();
12 r.at(0, 2);
13 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14 r.at(CARD_SY - 1, 2);
15 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20 let mut r = r.region(y, x, CARD_SY, CARD_SX);
21 r.hfb(27);
22 for y in 0..CARD_SY {
23 r.at(y, 0).text("||||||");
24 }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29 let mut r = r.region(y, x, CARD_SY, CARD_SX);
30 r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35 let mut r = r.region(y, x, CARD_SY, CARD_SX);
36 r.hfb(70);
37 for y in 0..CARD_SY {
38 r.at(y, 0).text("::::::");
39 }
40}More examples
172 fn draw_scores(&mut self, cx: CX![]) {
173 let mut score = self.score;
174 let mut last_score = self.last_score;
175 if let Some(mut r) = self.scores_tile.full(cx) {
176 r.hfb(176).clear_all();
177 r.hfb(7).text("SCORES");
178 let mut y = 1;
179 let last_y = r.sy() - 1;
180 let mut draw = move |hfb, sc| {
181 if y > 0 {
182 r.at(y, 0).hfb(hfb);
183 write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184 y += 1;
185 }
186 };
187
188 for &s0 in &self.scores {
189 if let Some(curr) = score
190 && (curr < s0 || y == last_y)
191 {
192 draw(7, curr);
193 score = None;
194 }
195 if Some(s0) == last_score {
196 draw(173, s0);
197 last_score = None;
198 } else {
199 draw(176, s0);
200 }
201 }
202 if let Some(s1) = score {
203 draw(173, s1);
204 }
205 }
206 }
207
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }
238
239 fn draw_instructions(mut r: Region<'_>) {
240 const TEXT: &str = r"
241
242 Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243 corresponding column; [+]/[=] or [-] Increase or decrease
244 number of selected cards; [1] to [9] If the same digit as the
245 selecting keypress, move selected cards to top, if another
246 digit, move as many of selected cards as possible to that
247 column; [Space] Bring a new card down from the stock pile;
248 [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250 Rules: The aim is to move all the cards to the top area, the
251 spaces the the right of the stock pile. Each top pile must
252 consist of one suit only, stacked in order from Ace up to
253 King. The cards in the main area can be moved around:
254 sequences of one or more cards of the same suit may be moved
255 on top of a card with the next-higher number, of any suit.
256 The bulk of the cards are in the stock pile in the top-left.
257 Cards may be brought down from there using Space.
258
259 ";
260 let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261 let n_lines = lines.len();
262 let oy = (r.sy() - n_lines as i32) / 2;
263 for (i, line) in lines.iter().enumerate() {
264 r.at(i as i32 + oy, 7);
265 let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266 r.text(&line);
267 }
268 }
269
270 fn input(&mut self, cx: CX![], key: Key) {
271 match key {
272 Key::Ctrl('L') => self.redraw(cx),
273 Key::Ctrl('C') => stop!(cx),
274 Key::Pr('q' | 'Q') => {
275 if self.game.is_some() {
276 self.game = None;
277 self.score = None;
278 self.layout(cx);
279 } else {
280 stop!(cx);
281 }
282 }
283 Key::Pr('n' | 'N') => {
284 self.game = Some(Game::new(
285 cx.now(),
286 ret_some_to!([cx], game_finished() as (Duration)),
287 ));
288 self.last_score = None;
289 self.update_score(cx);
290 self.layout(cx);
291 }
292 Key::Pr('<') => {
293 self.adjust -= 1;
294 self.save_scores().expect("Failed to save scores");
295 self.layout(cx);
296 }
297 Key::Pr('>') => {
298 self.adjust += 1;
299 self.save_scores().expect("Failed to save scores");
300 self.layout(cx);
301 }
302 _ => {
303 if let Some(game) = &mut self.game {
304 game.input(cx, key);
305 }
306 }
307 }
308 }
309
310 fn update_score(&mut self, cx: CX![]) {
311 if let Some(game) = &mut self.game {
312 let dur = game.score(cx);
313 let score = dur.as_secs() as i32;
314 let to_wait = if Some(score) != self.score {
315 self.score = Some(score);
316 self.draw_scores(cx);
317
318 // Update 100ms after next change
319 1_000_000_000 - dur.subsec_nanos() + 100_000_000
320 } else {
321 1_000_000_000
322 };
323 after!(Duration::from_nanos(to_wait as u64), [cx], update_score());
324 }
325 }
326
327 fn game_finished(&mut self, cx: CX![], dur: Duration) {
328 let score = dur.as_secs() as i32;
329 self.scores.push(score);
330 self.scores.sort_unstable();
331 self.save_scores().expect("Failed to save scores");
332 self.last_score = Some(score);
333 self.score = None;
334 self.game = None;
335 self.layout(cx);
336 }
337
338 fn score_path() -> PathBuf {
339 let mut path = std::env::home_dir().unwrap_or_default();
340 path.push(".teeclub-scores");
341 path
342 }
343
344 fn load_scores(&mut self) -> Option<()> {
345 let data = std::fs::read_to_string(Self::score_path()).ok()?;
346 let mut it = data.split(char::is_whitespace);
347 while let Some(tok) = it.next() {
348 match tok {
349 "adjust:" => {
350 self.adjust = it.next()?.parse::<i32>().ok()?;
351 }
352 "scores[" => {
353 self.scores = Vec::new();
354 loop {
355 match it.next()? {
356 "]" => break,
357 v => self.scores.push(v.parse::<i32>().ok()?),
358 }
359 }
360 self.scores.sort_unstable();
361 }
362 "" => (),
363 _ => return None,
364 }
365 }
366 Some(())
367 }
368
369 fn save_scores(&mut self) -> std::io::Result<()> {
370 let mut out = Vec::new();
371 let _ = writeln!(out, "adjust: {}", self.adjust);
372 let _ = writeln!(out, "scores[");
373 for sc in self.scores.iter().take(200) {
374 let _ = writeln!(out, "{sc}");
375 }
376 let _ = writeln!(out, "]");
377 std::fs::write(Self::score_path(), out)
378 }
379}
380
381/// Game state
382#[derive(Eq, PartialEq)]
383struct State {
384 pile: [Hand; 9],
385 stack: [Hand; 9],
386}
387
388impl State {
389 fn pack(&self) -> Vec<u8> {
390 let mut out = Vec::new();
391 for p in &self.pile {
392 p.pack(&mut out);
393 }
394 for s in &self.stack {
395 s.pack(&mut out);
396 }
397 out
398 }
399
400 fn unpack(mut data: &[u8]) -> Self {
401 let mut pile: [Hand; 9] = Default::default();
402 let mut stack: [Hand; 9] = Default::default();
403 for p in &mut pile {
404 *p = Hand::unpack(&mut data);
405 }
406 for s in &mut stack {
407 *s = Hand::unpack(&mut data);
408 }
409 Self { pile, stack }
410 }
411}
412
413/// Game state and gameplay handling
414struct Game {
415 tile: Tile,
416 history: Vec<Vec<u8>>,
417 state: State,
418 select: Option<(usize, usize)>, // (index, count)
419 dur: Duration,
420 activity: Instant,
421 ret: Option<Ret<Duration>>, // Report score for game completion
422}
423
424impl Game {
425 fn new(start: Instant, ret: Ret<Duration>) -> Self {
426 let seed = std::time::SystemTime::UNIX_EPOCH
427 .elapsed()
428 .map(|d| d.as_nanos() as u64)
429 .unwrap_or(0);
430
431 let mut rand = Rand32::new(seed);
432 let mut pile: [Hand; 9] = Default::default();
433 let mut stack: [Hand; 9] = Default::default();
434
435 let p0 = &mut pile[0];
436 p0.add_deck();
437 p0.add_deck();
438 p0.shuffle(&mut rand);
439 p0.shuffle(&mut rand);
440
441 for _ in 0..5 {
442 for s in &mut stack {
443 if let Some(card) = p0.pick_last() {
444 s.add(card);
445 }
446 }
447 }
448
449 Self {
450 tile: Tile::default(),
451 history: Vec::new(),
452 state: State { pile, stack },
453 select: None,
454 dur: Duration::from_secs(0),
455 activity: start,
456 ret: Some(ret),
457 }
458 }
459
460 fn select(&mut self, core: &mut Core, i: usize) {
461 let s = &self.state.stack[i];
462 let len = s.len();
463 if len == 0 {
464 self.select = None;
465 } else {
466 let mut count = 1;
467 while count < len && s[len - count].inc == Some(s[len - count - 1]) {
468 count += 1;
469 }
470 self.select = Some((i, count));
471 }
472 self.draw(core);
473 }
474
475 fn sel_inc(&mut self, core: &mut Core) {
476 if let Some((i, count)) = self.select {
477 let s = &self.state.stack[i];
478 let len = s.len();
479 if count < len && s[len - count].inc == Some(s[len - count - 1]) {
480 self.select = Some((i, count + 1));
481 self.draw(core);
482 }
483 }
484 }
485
486 fn sel_dec(&mut self, core: &mut Core) {
487 if let Some(sel) = &mut self.select {
488 let i = sel.0;
489 let count = sel.1;
490 if count <= 1 {
491 self.select = None;
492 } else {
493 self.select = Some((i, count - 1));
494 }
495 self.draw(core);
496 }
497 }
498
499 /// Mark this state by saving it in history, available to undo
500 /// later
501 fn mark(&mut self) {
502 let v = self.state.pack();
503 if let Some(last) = self.history.last()
504 && *last == v
505 {
506 return;
507 }
508 self.history.push(v);
509 }
510
511 /// Restore most recent marked state and remove it from history
512 fn undo(&mut self, core: &mut Core) {
513 if let Some(data) = self.history.pop() {
514 self.state = State::unpack(&data);
515 self.select = None;
516 self.draw(core);
517 }
518 }
519
520 /// Move selected cards to the given stack or to top if the stack
521 /// is the selected stack
522 fn move_to(&mut self, core: &mut Core, to: usize) {
523 let Some((fr, cnt)) = self.select else {
524 return;
525 };
526 self.select = None;
527 self.mark();
528
529 let s = &mut self.state;
530 let Some(fr_last) = s.stack[fr].peek_last() else {
531 return;
532 };
533
534 if fr == to {
535 // Move to top
536 for pi in (1..9).rev() {
537 let do_move = if let Some(to_last) = s.pile[pi].peek_last() {
538 to_last.inc == Some(fr_last)
539 } else {
540 fr_last.num == 1
541 };
542 if do_move {
543 for _ in 0..cnt {
544 if let Some(card) = s.stack[fr].pick_last() {
545 s.pile[pi].add(card);
546 }
547 }
548 break;
549 }
550 }
551 if self.is_finished()
552 && let Some(ret) = self.ret.take()
553 {
554 ret!([ret], self.score(core));
555 }
556 } else {
557 // Move to another pile
558 let mut copy_cnt = cnt;
559 if let Some(to_last) = s.stack[to].peek_last() {
560 copy_cnt = copy_cnt.min(to_last.num.saturating_sub(fr_last.num) as usize);
561 if to_last.num != fr_last.num + copy_cnt as u8 {
562 copy_cnt = 0;
563 }
564 }
565
566 let mut tmp = Vec::new();
567 for _ in 0..copy_cnt {
568 tmp.push(s.stack[fr].pick_last().unwrap());
569 }
570 while let Some(card) = tmp.pop() {
571 s.stack[to].add(card);
572 }
573 }
574 self.draw(core);
575 }
576
577 /// Move card down from stock pile
578 fn move_down(&mut self, core: &mut Core) {
579 self.mark();
580 if let Some(card) = self.state.pile[0].pick_last() {
581 self.state.stack[0].add(card);
582 self.select = None;
583 self.draw(core);
584 }
585 }
586
587 fn is_finished(&mut self) -> bool {
588 for i in 1..9 {
589 if self.state.pile[i].len() != 13 {
590 return false;
591 }
592 }
593 true
594 }
595
596 /// Set the tile to use for drawing, and redraw
597 fn layout(&mut self, core: &mut Core, tile: Tile) {
598 self.tile = tile;
599 self.draw(core);
600 }
601
602 /// Draw the playing area
603 fn draw(&mut self, core: &mut Core) {
604 if let Some(mut r) = self.tile.full(core) {
605 r.clear_all_99();
606
607 for col in 0..9 {
608 let x = 1 + col as i32 * 8;
609
610 // Pile
611 if let Some(card) = self.state.pile[col].peek_last() {
612 if col == 0 {
613 // Facedown pile
614 draw::card_back(&mut r, 0, x);
615 } else {
616 draw::card(&mut r, 0, x, card);
617 }
618 } else {
619 draw::card_space(&mut r, 0, x);
620 }
621
622 // Index number
623 r.hfb(7)
624 .at(CARD_SY, x)
625 .hfb(70)
626 .char((49 + col as u8) as char);
627
628 // Stack
629 let y0 = CARD_SY + 1;
630 let y1 = r.sy();
631 let hand = &self.state.stack[col];
632 draw::card_space(&mut r, y0, x);
633
634 let selected = self
635 .select
636 .map(|(index, count)| if col == index { count } else { 0 })
637 .unwrap_or(0);
638 let len = hand.len();
639 let sel_i = len.saturating_sub(selected);
640 let space = y1 - y0 - 1;
641 let mut y = y0;
642 let mut i = 0;
643 if len as i32 + CARD_SY > space {
644 draw::card_dots(&mut r, y, x);
645 y += 1;
646 i = len - (space - CARD_SY) as usize;
647 }
648 while i < hand.len() {
649 let card = hand[i];
650 if i >= sel_i {
651 draw::card(&mut r, y + 1, x + 1, card);
652 } else {
653 draw::card(&mut r, y, x, card);
654 }
655 i += 1;
656 y += 1;
657 }
658 }
659 }
660 }97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }85 fn redraw(&mut self, cx: CX![]) {
86 if let Some(ref tsh) = self.tsh {
87 let out = tsh.output(cx);
88 out.attr_99().cursor_show().scroll_up().save_cleanup();
89 out.cursor_hide().clear_all_99_and_remote();
90
91 // Draw fixed stuff here, then break out and store
92 // sub-tiles for areas that need to be updated later
93 let tile = tsh.tile(cx);
94 let sx = tile.sx();
95 let sy = tile.sy();
96 let split_y1 = sy - self.font.sy as i32;
97 let split_y0 = split_y1.min(10);
98 let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99 let spare = sx.saturating_sub(80);
100 let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101 (spare / 2, 0)
102 } else {
103 (0, spare)
104 };
105 let (_, rest) = top.split_x(ox);
106 let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108 if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109 r.hfb(172).clear_all();
110 let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120 r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121 }
122 self.cells_tile = cells_tile;
123 self.param_tile = param_tile;
124 self.mini_tile = mini_tile;
125
126 self.draw_cells(cx);
127 self.draw_mini(cx);
128 self.draw_param(cx);
129 }
130 }
131
132 fn draw_param(&mut self, cx: CX![]) {
133 if let Some(mut r) = self.param_tile.full(cx) {
134 r.hfb(172).clear_all();
135
136 // Use sub-region to clip the filename if it's too long
137 r.region(0, 0, 1, 39)
138 .hfb(172)
139 .text("File: ")
140 .text(&self.font.path.to_string_lossy());
141
142 let count = self.font.index_limit();
143 let index = self.index;
144
145 let state = if let Some(ref err) = self.save_error {
146 err
147 } else if self.unsaved {
148 "UNSAVED"
149 } else {
150 ""
151 };
152 let cp = self.font.chars[index].cp;
153 let ch = match cp {
154 32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155 _ => '?',
156 };
157 let sx = self.font.sx;
158 let sy = self.font.sy;
159
160 r.at(1, 0);
161 let _ = writeln!(r, "Size: {sx} x {sy}, {count} glyphs");
162 let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163 let _ = writeln!(r, "Mode: {}", self.mode.caps());
164 let _ = writeln!(r, "State: {}", state);
165 }
166 }
167
168 fn draw_cells(&mut self, cx: CX![]) {
169 if let Some(mut full) = self.cells_tile.full(cx) {
170 let sx = full.sx();
171 full.clear_all_99();
172
173 let font_sx = self.font.sx;
174 let font_sy = self.font.sy;
175 let cell_wid = font_sx * 2;
176 let r = full.region(
177 0,
178 (sx - cell_wid as i32) / 2,
179 font_sy as i32,
180 cell_wid as i32,
181 );
182 draw_cell(
183 &self.font,
184 r,
185 self.index,
186 171,
187 7,
188 Some((self.cy, self.cx, self.mode)),
189 );
190 let max = self.font.index_limit();
191 for i in 1..=self.index {
192 let r = full.region(
193 0,
194 (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195 font_sy as i32,
196 cell_wid as i32,
197 );
198 if !r.is_visible() {
199 break;
200 }
201 draw_cell(&self.font, r, self.index - i, 70, 6, None);
202 }
203 for i in 1..(max - self.index) {
204 let r = full.region(
205 0,
206 (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207 font_sy as i32,
208 cell_wid as i32,
209 );
210 if !r.is_visible() {
211 break;
212 }
213 draw_cell(&self.font, r, self.index + i, 70, 6, None);
214 }
215 }
216 }
217
218 fn draw_mini(&mut self, cx: CX![]) {
219 if let Some(mut full) = self.mini_tile.full(cx) {
220 full.clear_all_99();
221 let sx = full.sx() * 2;
222 let sy = full.sy() * 4;
223 let font_sx = self.font.sx as i32;
224 let font_sy = self.font.sy as i32;
225 let oy = sy.saturating_sub(font_sy) / 2;
226 let mut ox = (sx - font_sx) / 2;
227 let mut index = self.index;
228 while ox > 0 && index > 0 {
229 ox -= font_sx;
230 index -= 1;
231 }
232 while ox < sx {
233 draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234 ox += font_sx;
235 index += 1;
236 }
237 }
238 }
239
240 fn draw_all(&mut self, cx: CX![]) {
241 self.draw_cells(cx);
242 self.draw_mini(cx);
243 self.draw_param(cx);
244 }
245
246 fn input(&mut self, cx: CX![], key: Key) {
247 match key {
248 Key::Ctrl('L') => self.redraw(cx),
249 Key::Ctrl('C') => stop!(cx),
250 Key::Left => {
251 if self.cx > 0 {
252 self.move_or_draw(cx, 0, -1)
253 }
254 }
255 Key::Right => {
256 if self.cx + 1 < self.font.sx {
257 self.move_or_draw(cx, 0, 1)
258 }
259 }
260 Key::Up => {
261 if self.cy > 0 {
262 self.move_or_draw(cx, -1, 0)
263 }
264 }
265 Key::Down => {
266 if self.cy + 1 < self.font.sy {
267 self.move_or_draw(cx, 1, 0)
268 }
269 }
270 Key::PgUp => {
271 if self.index > 0 {
272 self.index -= 1;
273 self.draw_all(cx);
274 }
275 }
276 Key::PgDn => {
277 if self.index + 1 < self.font.index_limit() {
278 self.index += 1;
279 self.draw_all(cx);
280 }
281 }
282 Key::Home => {
283 self.index = 0;
284 self.draw_all(cx);
285 }
286 Key::End => {
287 self.index = self.font.index_limit() - 1;
288 self.draw_all(cx);
289 }
290 Key::Pr(' ') => {
291 self.undo_push();
292 self.unsaved = true;
293 let curr = self.font.get(self.index, self.cy, self.cx);
294 self.font.set(self.index, self.cy, self.cx, !curr);
295 self.draw_all(cx);
296 }
297 Key::Pr('d') => {
298 self.mode = match self.mode {
299 Mode::Draw => Mode::Move,
300 _ => Mode::Draw,
301 };
302 self.draw_all(cx);
303 }
304 Key::Pr('r') => {
305 self.mode = match self.mode {
306 Mode::Roll => Mode::Move,
307 _ => Mode::Roll,
308 };
309 self.draw_all(cx);
310 }
311 Key::Pr('f') => {
312 self.undo_push();
313 self.unsaved = true;
314 self.font.fill(self.index, self.cy, self.cx, true, true);
315 self.draw_all(cx);
316 }
317 Key::Pr('h') => {
318 self.undo_push();
319 self.unsaved = true;
320 self.font.fill(self.index, self.cy, self.cx, false, true);
321 self.draw_all(cx);
322 }
323 Key::Pr('v') => {
324 self.undo_push();
325 self.unsaved = true;
326 self.font.fill(self.index, self.cy, self.cx, true, false);
327 self.draw_all(cx);
328 }
329 Key::Pr('u') => {
330 if let Some(font) = self.undo.pop_front() {
331 self.font = font;
332 let limit = self.font.index_limit();
333 self.index = self.index.min(limit - 1);
334 self.draw_all(cx);
335 }
336 }
337 Key::Pr('l') => {
338 self.font.rule_bm ^= 1 << self.cy;
339 self.unsaved = true;
340 self.draw_all(cx);
341 }
342 Key::Pr('n') => {
343 self.undo_push();
344 self.unsaved = true;
345 self.index = self.font.index_limit();
346 self.font.add_glyph();
347 self.draw_param(cx);
348 self.draw_cells(cx);
349 }
350 Key::Pr('c') => {
351 self.clip = self.font.get_glyph(self.index);
352 }
353 Key::Pr('P') => {
354 if !self.clip.is_empty() {
355 self.undo_push();
356 self.unsaved = true;
357 self.font.set_glyph(self.index, &self.clip);
358 self.draw_all(cx);
359 }
360 }
361 Key::Pr('S') => {
362 if let Err(e) = self.font.save() {
363 self.save_error = Some(e.to_string());
364 } else {
365 self.save_error = None;
366 self.unsaved = false;
367 }
368 self.draw_param(cx);
369 }
370 Key::Pr('Q') => {
371 if let Err(e) = self.font.save() {
372 fail!(cx, e);
373 } else {
374 stop!(cx);
375 }
376 }
377 _ => call!([self.term], bell()),
378 }
379 }
380
381 fn move_or_draw(&mut self, cx: CX![], dy: i32, dx: i32) {
382 match self.mode {
383 Mode::Move => {
384 self.cx = (self.cx as i32 + dx) as usize;
385 self.cy = (self.cy as i32 + dy) as usize;
386 self.draw_cells(cx);
387 }
388 Mode::Draw => {
389 self.undo_push();
390 self.unsaved = true;
391 let curr = self.font.get(self.index, self.cy, self.cx);
392 self.cx = (self.cx as i32 + dx) as usize;
393 self.cy = (self.cy as i32 + dy) as usize;
394 self.font.set(self.index, self.cy, self.cx, curr);
395 self.draw_all(cx);
396 }
397 Mode::Roll => {
398 self.undo_push();
399 if dx != 0 {
400 self.font.roll_horiz(self.index, dx);
401 }
402 if dy != 0 {
403 self.font.roll_vert(self.index, dy);
404 }
405 self.draw_all(cx);
406 }
407 }
408 }
409
410 fn undo_push(&mut self) {
411 self.undo.push_front(self.font.clone());
412 while self.undo.len() > 50 {
413 self.undo.pop_back();
414 }
415 }
416}
417
418fn draw_cell(
419 font: &Font,
420 mut r: Region,
421 index: usize,
422 black: u16,
423 white: u16,
424 curs: Option<(usize, usize, Mode)>,
425) {
426 let form = &font.chars[index].form;
427 for y in 0..font.sy {
428 let rule = ((font.rule_bm >> y) & 1) != 0;
429 for x in 0..font.sx {
430 r.hfb(if form[x + y * font.sx] { white } else { black });
431 let mut text = if rule { "--" } else { " " };
432 if let Some((cy, cx, mode)) = curs
433 && cy == y
434 && cx == x
435 {
436 text = mode.cursor();
437 }
438 r.text(text);
439 }
440 }
441}Sourcepub fn at(&mut self, wy: i32, wx: i32) -> &mut Self
pub fn at(&mut self, wy: i32, wx: i32) -> &mut Self
Change the write position to the given position relative to region top-left.
Examples found in repository?
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10 let mut r = r.region(y, x, CARD_SY, CARD_SX);
11 r.hfb(card.hfb).clear_all();
12 r.at(0, 2);
13 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14 r.at(CARD_SY - 1, 2);
15 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20 let mut r = r.region(y, x, CARD_SY, CARD_SX);
21 r.hfb(27);
22 for y in 0..CARD_SY {
23 r.at(y, 0).text("||||||");
24 }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29 let mut r = r.region(y, x, CARD_SY, CARD_SX);
30 r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35 let mut r = r.region(y, x, CARD_SY, CARD_SX);
36 r.hfb(70);
37 for y in 0..CARD_SY {
38 r.at(y, 0).text("::::::");
39 }
40}More examples
172 fn draw_scores(&mut self, cx: CX![]) {
173 let mut score = self.score;
174 let mut last_score = self.last_score;
175 if let Some(mut r) = self.scores_tile.full(cx) {
176 r.hfb(176).clear_all();
177 r.hfb(7).text("SCORES");
178 let mut y = 1;
179 let last_y = r.sy() - 1;
180 let mut draw = move |hfb, sc| {
181 if y > 0 {
182 r.at(y, 0).hfb(hfb);
183 write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184 y += 1;
185 }
186 };
187
188 for &s0 in &self.scores {
189 if let Some(curr) = score
190 && (curr < s0 || y == last_y)
191 {
192 draw(7, curr);
193 score = None;
194 }
195 if Some(s0) == last_score {
196 draw(173, s0);
197 last_score = None;
198 } else {
199 draw(176, s0);
200 }
201 }
202 if let Some(s1) = score {
203 draw(173, s1);
204 }
205 }
206 }
207
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }
238
239 fn draw_instructions(mut r: Region<'_>) {
240 const TEXT: &str = r"
241
242 Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243 corresponding column; [+]/[=] or [-] Increase or decrease
244 number of selected cards; [1] to [9] If the same digit as the
245 selecting keypress, move selected cards to top, if another
246 digit, move as many of selected cards as possible to that
247 column; [Space] Bring a new card down from the stock pile;
248 [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250 Rules: The aim is to move all the cards to the top area, the
251 spaces the the right of the stock pile. Each top pile must
252 consist of one suit only, stacked in order from Ace up to
253 King. The cards in the main area can be moved around:
254 sequences of one or more cards of the same suit may be moved
255 on top of a card with the next-higher number, of any suit.
256 The bulk of the cards are in the stock pile in the top-left.
257 Cards may be brought down from there using Space.
258
259 ";
260 let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261 let n_lines = lines.len();
262 let oy = (r.sy() - n_lines as i32) / 2;
263 for (i, line) in lines.iter().enumerate() {
264 r.at(i as i32 + oy, 7);
265 let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266 r.text(&line);
267 }
268 }
269
270 fn input(&mut self, cx: CX![], key: Key) {
271 match key {
272 Key::Ctrl('L') => self.redraw(cx),
273 Key::Ctrl('C') => stop!(cx),
274 Key::Pr('q' | 'Q') => {
275 if self.game.is_some() {
276 self.game = None;
277 self.score = None;
278 self.layout(cx);
279 } else {
280 stop!(cx);
281 }
282 }
283 Key::Pr('n' | 'N') => {
284 self.game = Some(Game::new(
285 cx.now(),
286 ret_some_to!([cx], game_finished() as (Duration)),
287 ));
288 self.last_score = None;
289 self.update_score(cx);
290 self.layout(cx);
291 }
292 Key::Pr('<') => {
293 self.adjust -= 1;
294 self.save_scores().expect("Failed to save scores");
295 self.layout(cx);
296 }
297 Key::Pr('>') => {
298 self.adjust += 1;
299 self.save_scores().expect("Failed to save scores");
300 self.layout(cx);
301 }
302 _ => {
303 if let Some(game) = &mut self.game {
304 game.input(cx, key);
305 }
306 }
307 }
308 }
309
310 fn update_score(&mut self, cx: CX![]) {
311 if let Some(game) = &mut self.game {
312 let dur = game.score(cx);
313 let score = dur.as_secs() as i32;
314 let to_wait = if Some(score) != self.score {
315 self.score = Some(score);
316 self.draw_scores(cx);
317
318 // Update 100ms after next change
319 1_000_000_000 - dur.subsec_nanos() + 100_000_000
320 } else {
321 1_000_000_000
322 };
323 after!(Duration::from_nanos(to_wait as u64), [cx], update_score());
324 }
325 }
326
327 fn game_finished(&mut self, cx: CX![], dur: Duration) {
328 let score = dur.as_secs() as i32;
329 self.scores.push(score);
330 self.scores.sort_unstable();
331 self.save_scores().expect("Failed to save scores");
332 self.last_score = Some(score);
333 self.score = None;
334 self.game = None;
335 self.layout(cx);
336 }
337
338 fn score_path() -> PathBuf {
339 let mut path = std::env::home_dir().unwrap_or_default();
340 path.push(".teeclub-scores");
341 path
342 }
343
344 fn load_scores(&mut self) -> Option<()> {
345 let data = std::fs::read_to_string(Self::score_path()).ok()?;
346 let mut it = data.split(char::is_whitespace);
347 while let Some(tok) = it.next() {
348 match tok {
349 "adjust:" => {
350 self.adjust = it.next()?.parse::<i32>().ok()?;
351 }
352 "scores[" => {
353 self.scores = Vec::new();
354 loop {
355 match it.next()? {
356 "]" => break,
357 v => self.scores.push(v.parse::<i32>().ok()?),
358 }
359 }
360 self.scores.sort_unstable();
361 }
362 "" => (),
363 _ => return None,
364 }
365 }
366 Some(())
367 }
368
369 fn save_scores(&mut self) -> std::io::Result<()> {
370 let mut out = Vec::new();
371 let _ = writeln!(out, "adjust: {}", self.adjust);
372 let _ = writeln!(out, "scores[");
373 for sc in self.scores.iter().take(200) {
374 let _ = writeln!(out, "{sc}");
375 }
376 let _ = writeln!(out, "]");
377 std::fs::write(Self::score_path(), out)
378 }
379}
380
381/// Game state
382#[derive(Eq, PartialEq)]
383struct State {
384 pile: [Hand; 9],
385 stack: [Hand; 9],
386}
387
388impl State {
389 fn pack(&self) -> Vec<u8> {
390 let mut out = Vec::new();
391 for p in &self.pile {
392 p.pack(&mut out);
393 }
394 for s in &self.stack {
395 s.pack(&mut out);
396 }
397 out
398 }
399
400 fn unpack(mut data: &[u8]) -> Self {
401 let mut pile: [Hand; 9] = Default::default();
402 let mut stack: [Hand; 9] = Default::default();
403 for p in &mut pile {
404 *p = Hand::unpack(&mut data);
405 }
406 for s in &mut stack {
407 *s = Hand::unpack(&mut data);
408 }
409 Self { pile, stack }
410 }
411}
412
413/// Game state and gameplay handling
414struct Game {
415 tile: Tile,
416 history: Vec<Vec<u8>>,
417 state: State,
418 select: Option<(usize, usize)>, // (index, count)
419 dur: Duration,
420 activity: Instant,
421 ret: Option<Ret<Duration>>, // Report score for game completion
422}
423
424impl Game {
425 fn new(start: Instant, ret: Ret<Duration>) -> Self {
426 let seed = std::time::SystemTime::UNIX_EPOCH
427 .elapsed()
428 .map(|d| d.as_nanos() as u64)
429 .unwrap_or(0);
430
431 let mut rand = Rand32::new(seed);
432 let mut pile: [Hand; 9] = Default::default();
433 let mut stack: [Hand; 9] = Default::default();
434
435 let p0 = &mut pile[0];
436 p0.add_deck();
437 p0.add_deck();
438 p0.shuffle(&mut rand);
439 p0.shuffle(&mut rand);
440
441 for _ in 0..5 {
442 for s in &mut stack {
443 if let Some(card) = p0.pick_last() {
444 s.add(card);
445 }
446 }
447 }
448
449 Self {
450 tile: Tile::default(),
451 history: Vec::new(),
452 state: State { pile, stack },
453 select: None,
454 dur: Duration::from_secs(0),
455 activity: start,
456 ret: Some(ret),
457 }
458 }
459
460 fn select(&mut self, core: &mut Core, i: usize) {
461 let s = &self.state.stack[i];
462 let len = s.len();
463 if len == 0 {
464 self.select = None;
465 } else {
466 let mut count = 1;
467 while count < len && s[len - count].inc == Some(s[len - count - 1]) {
468 count += 1;
469 }
470 self.select = Some((i, count));
471 }
472 self.draw(core);
473 }
474
475 fn sel_inc(&mut self, core: &mut Core) {
476 if let Some((i, count)) = self.select {
477 let s = &self.state.stack[i];
478 let len = s.len();
479 if count < len && s[len - count].inc == Some(s[len - count - 1]) {
480 self.select = Some((i, count + 1));
481 self.draw(core);
482 }
483 }
484 }
485
486 fn sel_dec(&mut self, core: &mut Core) {
487 if let Some(sel) = &mut self.select {
488 let i = sel.0;
489 let count = sel.1;
490 if count <= 1 {
491 self.select = None;
492 } else {
493 self.select = Some((i, count - 1));
494 }
495 self.draw(core);
496 }
497 }
498
499 /// Mark this state by saving it in history, available to undo
500 /// later
501 fn mark(&mut self) {
502 let v = self.state.pack();
503 if let Some(last) = self.history.last()
504 && *last == v
505 {
506 return;
507 }
508 self.history.push(v);
509 }
510
511 /// Restore most recent marked state and remove it from history
512 fn undo(&mut self, core: &mut Core) {
513 if let Some(data) = self.history.pop() {
514 self.state = State::unpack(&data);
515 self.select = None;
516 self.draw(core);
517 }
518 }
519
520 /// Move selected cards to the given stack or to top if the stack
521 /// is the selected stack
522 fn move_to(&mut self, core: &mut Core, to: usize) {
523 let Some((fr, cnt)) = self.select else {
524 return;
525 };
526 self.select = None;
527 self.mark();
528
529 let s = &mut self.state;
530 let Some(fr_last) = s.stack[fr].peek_last() else {
531 return;
532 };
533
534 if fr == to {
535 // Move to top
536 for pi in (1..9).rev() {
537 let do_move = if let Some(to_last) = s.pile[pi].peek_last() {
538 to_last.inc == Some(fr_last)
539 } else {
540 fr_last.num == 1
541 };
542 if do_move {
543 for _ in 0..cnt {
544 if let Some(card) = s.stack[fr].pick_last() {
545 s.pile[pi].add(card);
546 }
547 }
548 break;
549 }
550 }
551 if self.is_finished()
552 && let Some(ret) = self.ret.take()
553 {
554 ret!([ret], self.score(core));
555 }
556 } else {
557 // Move to another pile
558 let mut copy_cnt = cnt;
559 if let Some(to_last) = s.stack[to].peek_last() {
560 copy_cnt = copy_cnt.min(to_last.num.saturating_sub(fr_last.num) as usize);
561 if to_last.num != fr_last.num + copy_cnt as u8 {
562 copy_cnt = 0;
563 }
564 }
565
566 let mut tmp = Vec::new();
567 for _ in 0..copy_cnt {
568 tmp.push(s.stack[fr].pick_last().unwrap());
569 }
570 while let Some(card) = tmp.pop() {
571 s.stack[to].add(card);
572 }
573 }
574 self.draw(core);
575 }
576
577 /// Move card down from stock pile
578 fn move_down(&mut self, core: &mut Core) {
579 self.mark();
580 if let Some(card) = self.state.pile[0].pick_last() {
581 self.state.stack[0].add(card);
582 self.select = None;
583 self.draw(core);
584 }
585 }
586
587 fn is_finished(&mut self) -> bool {
588 for i in 1..9 {
589 if self.state.pile[i].len() != 13 {
590 return false;
591 }
592 }
593 true
594 }
595
596 /// Set the tile to use for drawing, and redraw
597 fn layout(&mut self, core: &mut Core, tile: Tile) {
598 self.tile = tile;
599 self.draw(core);
600 }
601
602 /// Draw the playing area
603 fn draw(&mut self, core: &mut Core) {
604 if let Some(mut r) = self.tile.full(core) {
605 r.clear_all_99();
606
607 for col in 0..9 {
608 let x = 1 + col as i32 * 8;
609
610 // Pile
611 if let Some(card) = self.state.pile[col].peek_last() {
612 if col == 0 {
613 // Facedown pile
614 draw::card_back(&mut r, 0, x);
615 } else {
616 draw::card(&mut r, 0, x, card);
617 }
618 } else {
619 draw::card_space(&mut r, 0, x);
620 }
621
622 // Index number
623 r.hfb(7)
624 .at(CARD_SY, x)
625 .hfb(70)
626 .char((49 + col as u8) as char);
627
628 // Stack
629 let y0 = CARD_SY + 1;
630 let y1 = r.sy();
631 let hand = &self.state.stack[col];
632 draw::card_space(&mut r, y0, x);
633
634 let selected = self
635 .select
636 .map(|(index, count)| if col == index { count } else { 0 })
637 .unwrap_or(0);
638 let len = hand.len();
639 let sel_i = len.saturating_sub(selected);
640 let space = y1 - y0 - 1;
641 let mut y = y0;
642 let mut i = 0;
643 if len as i32 + CARD_SY > space {
644 draw::card_dots(&mut r, y, x);
645 y += 1;
646 i = len - (space - CARD_SY) as usize;
647 }
648 while i < hand.len() {
649 let card = hand[i];
650 if i >= sel_i {
651 draw::card(&mut r, y + 1, x + 1, card);
652 } else {
653 draw::card(&mut r, y, x, card);
654 }
655 i += 1;
656 y += 1;
657 }
658 }
659 }
660 }132 fn draw_param(&mut self, cx: CX![]) {
133 if let Some(mut r) = self.param_tile.full(cx) {
134 r.hfb(172).clear_all();
135
136 // Use sub-region to clip the filename if it's too long
137 r.region(0, 0, 1, 39)
138 .hfb(172)
139 .text("File: ")
140 .text(&self.font.path.to_string_lossy());
141
142 let count = self.font.index_limit();
143 let index = self.index;
144
145 let state = if let Some(ref err) = self.save_error {
146 err
147 } else if self.unsaved {
148 "UNSAVED"
149 } else {
150 ""
151 };
152 let cp = self.font.chars[index].cp;
153 let ch = match cp {
154 32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155 _ => '?',
156 };
157 let sx = self.font.sx;
158 let sy = self.font.sy;
159
160 r.at(1, 0);
161 let _ = writeln!(r, "Size: {sx} x {sy}, {count} glyphs");
162 let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163 let _ = writeln!(r, "Mode: {}", self.mode.caps());
164 let _ = writeln!(r, "State: {}", state);
165 }
166 }97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }Sourcepub fn char(&mut self, ch: char) -> &mut Self
pub fn char(&mut self, ch: char) -> &mut Self
Add a char to the region. This should represent a single
glyph. Glyphs using combining characters can’t be added this
way.
Examples found in repository?
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }
238
239 fn draw_instructions(mut r: Region<'_>) {
240 const TEXT: &str = r"
241
242 Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243 corresponding column; [+]/[=] or [-] Increase or decrease
244 number of selected cards; [1] to [9] If the same digit as the
245 selecting keypress, move selected cards to top, if another
246 digit, move as many of selected cards as possible to that
247 column; [Space] Bring a new card down from the stock pile;
248 [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250 Rules: The aim is to move all the cards to the top area, the
251 spaces the the right of the stock pile. Each top pile must
252 consist of one suit only, stacked in order from Ace up to
253 King. The cards in the main area can be moved around:
254 sequences of one or more cards of the same suit may be moved
255 on top of a card with the next-higher number, of any suit.
256 The bulk of the cards are in the stock pile in the top-left.
257 Cards may be brought down from there using Space.
258
259 ";
260 let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261 let n_lines = lines.len();
262 let oy = (r.sy() - n_lines as i32) / 2;
263 for (i, line) in lines.iter().enumerate() {
264 r.at(i as i32 + oy, 7);
265 let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266 r.text(&line);
267 }
268 }
269
270 fn input(&mut self, cx: CX![], key: Key) {
271 match key {
272 Key::Ctrl('L') => self.redraw(cx),
273 Key::Ctrl('C') => stop!(cx),
274 Key::Pr('q' | 'Q') => {
275 if self.game.is_some() {
276 self.game = None;
277 self.score = None;
278 self.layout(cx);
279 } else {
280 stop!(cx);
281 }
282 }
283 Key::Pr('n' | 'N') => {
284 self.game = Some(Game::new(
285 cx.now(),
286 ret_some_to!([cx], game_finished() as (Duration)),
287 ));
288 self.last_score = None;
289 self.update_score(cx);
290 self.layout(cx);
291 }
292 Key::Pr('<') => {
293 self.adjust -= 1;
294 self.save_scores().expect("Failed to save scores");
295 self.layout(cx);
296 }
297 Key::Pr('>') => {
298 self.adjust += 1;
299 self.save_scores().expect("Failed to save scores");
300 self.layout(cx);
301 }
302 _ => {
303 if let Some(game) = &mut self.game {
304 game.input(cx, key);
305 }
306 }
307 }
308 }
309
310 fn update_score(&mut self, cx: CX![]) {
311 if let Some(game) = &mut self.game {
312 let dur = game.score(cx);
313 let score = dur.as_secs() as i32;
314 let to_wait = if Some(score) != self.score {
315 self.score = Some(score);
316 self.draw_scores(cx);
317
318 // Update 100ms after next change
319 1_000_000_000 - dur.subsec_nanos() + 100_000_000
320 } else {
321 1_000_000_000
322 };
323 after!(Duration::from_nanos(to_wait as u64), [cx], update_score());
324 }
325 }
326
327 fn game_finished(&mut self, cx: CX![], dur: Duration) {
328 let score = dur.as_secs() as i32;
329 self.scores.push(score);
330 self.scores.sort_unstable();
331 self.save_scores().expect("Failed to save scores");
332 self.last_score = Some(score);
333 self.score = None;
334 self.game = None;
335 self.layout(cx);
336 }
337
338 fn score_path() -> PathBuf {
339 let mut path = std::env::home_dir().unwrap_or_default();
340 path.push(".teeclub-scores");
341 path
342 }
343
344 fn load_scores(&mut self) -> Option<()> {
345 let data = std::fs::read_to_string(Self::score_path()).ok()?;
346 let mut it = data.split(char::is_whitespace);
347 while let Some(tok) = it.next() {
348 match tok {
349 "adjust:" => {
350 self.adjust = it.next()?.parse::<i32>().ok()?;
351 }
352 "scores[" => {
353 self.scores = Vec::new();
354 loop {
355 match it.next()? {
356 "]" => break,
357 v => self.scores.push(v.parse::<i32>().ok()?),
358 }
359 }
360 self.scores.sort_unstable();
361 }
362 "" => (),
363 _ => return None,
364 }
365 }
366 Some(())
367 }
368
369 fn save_scores(&mut self) -> std::io::Result<()> {
370 let mut out = Vec::new();
371 let _ = writeln!(out, "adjust: {}", self.adjust);
372 let _ = writeln!(out, "scores[");
373 for sc in self.scores.iter().take(200) {
374 let _ = writeln!(out, "{sc}");
375 }
376 let _ = writeln!(out, "]");
377 std::fs::write(Self::score_path(), out)
378 }
379}
380
381/// Game state
382#[derive(Eq, PartialEq)]
383struct State {
384 pile: [Hand; 9],
385 stack: [Hand; 9],
386}
387
388impl State {
389 fn pack(&self) -> Vec<u8> {
390 let mut out = Vec::new();
391 for p in &self.pile {
392 p.pack(&mut out);
393 }
394 for s in &self.stack {
395 s.pack(&mut out);
396 }
397 out
398 }
399
400 fn unpack(mut data: &[u8]) -> Self {
401 let mut pile: [Hand; 9] = Default::default();
402 let mut stack: [Hand; 9] = Default::default();
403 for p in &mut pile {
404 *p = Hand::unpack(&mut data);
405 }
406 for s in &mut stack {
407 *s = Hand::unpack(&mut data);
408 }
409 Self { pile, stack }
410 }
411}
412
413/// Game state and gameplay handling
414struct Game {
415 tile: Tile,
416 history: Vec<Vec<u8>>,
417 state: State,
418 select: Option<(usize, usize)>, // (index, count)
419 dur: Duration,
420 activity: Instant,
421 ret: Option<Ret<Duration>>, // Report score for game completion
422}
423
424impl Game {
425 fn new(start: Instant, ret: Ret<Duration>) -> Self {
426 let seed = std::time::SystemTime::UNIX_EPOCH
427 .elapsed()
428 .map(|d| d.as_nanos() as u64)
429 .unwrap_or(0);
430
431 let mut rand = Rand32::new(seed);
432 let mut pile: [Hand; 9] = Default::default();
433 let mut stack: [Hand; 9] = Default::default();
434
435 let p0 = &mut pile[0];
436 p0.add_deck();
437 p0.add_deck();
438 p0.shuffle(&mut rand);
439 p0.shuffle(&mut rand);
440
441 for _ in 0..5 {
442 for s in &mut stack {
443 if let Some(card) = p0.pick_last() {
444 s.add(card);
445 }
446 }
447 }
448
449 Self {
450 tile: Tile::default(),
451 history: Vec::new(),
452 state: State { pile, stack },
453 select: None,
454 dur: Duration::from_secs(0),
455 activity: start,
456 ret: Some(ret),
457 }
458 }
459
460 fn select(&mut self, core: &mut Core, i: usize) {
461 let s = &self.state.stack[i];
462 let len = s.len();
463 if len == 0 {
464 self.select = None;
465 } else {
466 let mut count = 1;
467 while count < len && s[len - count].inc == Some(s[len - count - 1]) {
468 count += 1;
469 }
470 self.select = Some((i, count));
471 }
472 self.draw(core);
473 }
474
475 fn sel_inc(&mut self, core: &mut Core) {
476 if let Some((i, count)) = self.select {
477 let s = &self.state.stack[i];
478 let len = s.len();
479 if count < len && s[len - count].inc == Some(s[len - count - 1]) {
480 self.select = Some((i, count + 1));
481 self.draw(core);
482 }
483 }
484 }
485
486 fn sel_dec(&mut self, core: &mut Core) {
487 if let Some(sel) = &mut self.select {
488 let i = sel.0;
489 let count = sel.1;
490 if count <= 1 {
491 self.select = None;
492 } else {
493 self.select = Some((i, count - 1));
494 }
495 self.draw(core);
496 }
497 }
498
499 /// Mark this state by saving it in history, available to undo
500 /// later
501 fn mark(&mut self) {
502 let v = self.state.pack();
503 if let Some(last) = self.history.last()
504 && *last == v
505 {
506 return;
507 }
508 self.history.push(v);
509 }
510
511 /// Restore most recent marked state and remove it from history
512 fn undo(&mut self, core: &mut Core) {
513 if let Some(data) = self.history.pop() {
514 self.state = State::unpack(&data);
515 self.select = None;
516 self.draw(core);
517 }
518 }
519
520 /// Move selected cards to the given stack or to top if the stack
521 /// is the selected stack
522 fn move_to(&mut self, core: &mut Core, to: usize) {
523 let Some((fr, cnt)) = self.select else {
524 return;
525 };
526 self.select = None;
527 self.mark();
528
529 let s = &mut self.state;
530 let Some(fr_last) = s.stack[fr].peek_last() else {
531 return;
532 };
533
534 if fr == to {
535 // Move to top
536 for pi in (1..9).rev() {
537 let do_move = if let Some(to_last) = s.pile[pi].peek_last() {
538 to_last.inc == Some(fr_last)
539 } else {
540 fr_last.num == 1
541 };
542 if do_move {
543 for _ in 0..cnt {
544 if let Some(card) = s.stack[fr].pick_last() {
545 s.pile[pi].add(card);
546 }
547 }
548 break;
549 }
550 }
551 if self.is_finished()
552 && let Some(ret) = self.ret.take()
553 {
554 ret!([ret], self.score(core));
555 }
556 } else {
557 // Move to another pile
558 let mut copy_cnt = cnt;
559 if let Some(to_last) = s.stack[to].peek_last() {
560 copy_cnt = copy_cnt.min(to_last.num.saturating_sub(fr_last.num) as usize);
561 if to_last.num != fr_last.num + copy_cnt as u8 {
562 copy_cnt = 0;
563 }
564 }
565
566 let mut tmp = Vec::new();
567 for _ in 0..copy_cnt {
568 tmp.push(s.stack[fr].pick_last().unwrap());
569 }
570 while let Some(card) = tmp.pop() {
571 s.stack[to].add(card);
572 }
573 }
574 self.draw(core);
575 }
576
577 /// Move card down from stock pile
578 fn move_down(&mut self, core: &mut Core) {
579 self.mark();
580 if let Some(card) = self.state.pile[0].pick_last() {
581 self.state.stack[0].add(card);
582 self.select = None;
583 self.draw(core);
584 }
585 }
586
587 fn is_finished(&mut self) -> bool {
588 for i in 1..9 {
589 if self.state.pile[i].len() != 13 {
590 return false;
591 }
592 }
593 true
594 }
595
596 /// Set the tile to use for drawing, and redraw
597 fn layout(&mut self, core: &mut Core, tile: Tile) {
598 self.tile = tile;
599 self.draw(core);
600 }
601
602 /// Draw the playing area
603 fn draw(&mut self, core: &mut Core) {
604 if let Some(mut r) = self.tile.full(core) {
605 r.clear_all_99();
606
607 for col in 0..9 {
608 let x = 1 + col as i32 * 8;
609
610 // Pile
611 if let Some(card) = self.state.pile[col].peek_last() {
612 if col == 0 {
613 // Facedown pile
614 draw::card_back(&mut r, 0, x);
615 } else {
616 draw::card(&mut r, 0, x, card);
617 }
618 } else {
619 draw::card_space(&mut r, 0, x);
620 }
621
622 // Index number
623 r.hfb(7)
624 .at(CARD_SY, x)
625 .hfb(70)
626 .char((49 + col as u8) as char);
627
628 // Stack
629 let y0 = CARD_SY + 1;
630 let y1 = r.sy();
631 let hand = &self.state.stack[col];
632 draw::card_space(&mut r, y0, x);
633
634 let selected = self
635 .select
636 .map(|(index, count)| if col == index { count } else { 0 })
637 .unwrap_or(0);
638 let len = hand.len();
639 let sel_i = len.saturating_sub(selected);
640 let space = y1 - y0 - 1;
641 let mut y = y0;
642 let mut i = 0;
643 if len as i32 + CARD_SY > space {
644 draw::card_dots(&mut r, y, x);
645 y += 1;
646 i = len - (space - CARD_SY) as usize;
647 }
648 while i < hand.len() {
649 let card = hand[i];
650 if i >= sel_i {
651 draw::card(&mut r, y + 1, x + 1, card);
652 } else {
653 draw::card(&mut r, y, x, card);
654 }
655 i += 1;
656 y += 1;
657 }
658 }
659 }
660 }Sourcepub fn skip(&mut self, n: i32) -> &mut Self
pub fn skip(&mut self, n: i32) -> &mut Self
Skip the write position N cells forwards without overwriting any characters
Examples found in repository?
97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }Sourcepub fn newline(&mut self) -> &mut Self
pub fn newline(&mut self) -> &mut Self
Move write position to the start of the next row. Note that writing a full line leaves the write position just past the end of the line, but not on the next line, so calling this method correctly moves to the next line in that case.
Sourcepub fn clear_all_99(&mut self) -> &mut Self
pub fn clear_all_99(&mut self) -> &mut Self
Clear the whole region to the default colour-pair (99). The write position is set to top-left, and the current colour is set to 99.
Examples found in repository?
122 fn redraw(&mut self, cx: CX![]) {
123 let Some(ref tsh) = self.tsh else {
124 return;
125 };
126 let mut tile = tsh.tile(cx);
127 if let Some(mut r) = tile.full(cx) {
128 r.clear_all_99();
129 }
130 after!(Duration::from_millis(10), [cx], layout());
131 }
132
133 fn layout(&mut self, cx: CX![]) {
134 let Some(ref tsh) = self.tsh else {
135 return;
136 };
137
138 let mut tile = tsh.tile(cx);
139 if let Some(mut r) = tile.full(cx) {
140 r.clear_all_99();
141 }
142 let left_gap = 0.max(tile.sx() - 80) / 2;
143 let right_off = (left_gap + 80).min(tile.sx());
144 let right_gap = tile.sx() - right_off;
145 self.adjust = self.adjust.max(-left_gap).min(right_gap);
146 let left_gap = left_gap + self.adjust;
147 let right_off = (left_gap + 80).min(tile.sx());
148
149 let (_, mid, _) = tile.split_xx(left_gap, right_off);
150 let (main, scores) = mid.split_x(74);
151 self.scores_tile = scores;
152 let sy = main.sy();
153 if let Some(game) = &mut self.game {
154 game.layout(cx, main);
155 } else {
156 let logo_sy = 9;
157 let instr_sy = 16;
158 let gap = (sy - logo_sy - instr_sy) / 3;
159 let (_, mut logo, rest) = main.split_yy(gap, gap + logo_sy);
160 let (_, mut text, _) = rest.split_yy(gap, gap + instr_sy);
161 if let Some(r) = logo.full(cx) {
162 self.draw_logo(r);
163 }
164 if let Some(r) = text.full(cx) {
165 Self::draw_instructions(r);
166 }
167 }
168
169 self.draw_scores(cx);
170 }
171
172 fn draw_scores(&mut self, cx: CX![]) {
173 let mut score = self.score;
174 let mut last_score = self.last_score;
175 if let Some(mut r) = self.scores_tile.full(cx) {
176 r.hfb(176).clear_all();
177 r.hfb(7).text("SCORES");
178 let mut y = 1;
179 let last_y = r.sy() - 1;
180 let mut draw = move |hfb, sc| {
181 if y > 0 {
182 r.at(y, 0).hfb(hfb);
183 write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184 y += 1;
185 }
186 };
187
188 for &s0 in &self.scores {
189 if let Some(curr) = score
190 && (curr < s0 || y == last_y)
191 {
192 draw(7, curr);
193 score = None;
194 }
195 if Some(s0) == last_score {
196 draw(173, s0);
197 last_score = None;
198 } else {
199 draw(176, s0);
200 }
201 }
202 if let Some(s1) = score {
203 draw(173, s1);
204 }
205 }
206 }
207
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }
238
239 fn draw_instructions(mut r: Region<'_>) {
240 const TEXT: &str = r"
241
242 Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243 corresponding column; [+]/[=] or [-] Increase or decrease
244 number of selected cards; [1] to [9] If the same digit as the
245 selecting keypress, move selected cards to top, if another
246 digit, move as many of selected cards as possible to that
247 column; [Space] Bring a new card down from the stock pile;
248 [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250 Rules: The aim is to move all the cards to the top area, the
251 spaces the the right of the stock pile. Each top pile must
252 consist of one suit only, stacked in order from Ace up to
253 King. The cards in the main area can be moved around:
254 sequences of one or more cards of the same suit may be moved
255 on top of a card with the next-higher number, of any suit.
256 The bulk of the cards are in the stock pile in the top-left.
257 Cards may be brought down from there using Space.
258
259 ";
260 let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261 let n_lines = lines.len();
262 let oy = (r.sy() - n_lines as i32) / 2;
263 for (i, line) in lines.iter().enumerate() {
264 r.at(i as i32 + oy, 7);
265 let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266 r.text(&line);
267 }
268 }
269
270 fn input(&mut self, cx: CX![], key: Key) {
271 match key {
272 Key::Ctrl('L') => self.redraw(cx),
273 Key::Ctrl('C') => stop!(cx),
274 Key::Pr('q' | 'Q') => {
275 if self.game.is_some() {
276 self.game = None;
277 self.score = None;
278 self.layout(cx);
279 } else {
280 stop!(cx);
281 }
282 }
283 Key::Pr('n' | 'N') => {
284 self.game = Some(Game::new(
285 cx.now(),
286 ret_some_to!([cx], game_finished() as (Duration)),
287 ));
288 self.last_score = None;
289 self.update_score(cx);
290 self.layout(cx);
291 }
292 Key::Pr('<') => {
293 self.adjust -= 1;
294 self.save_scores().expect("Failed to save scores");
295 self.layout(cx);
296 }
297 Key::Pr('>') => {
298 self.adjust += 1;
299 self.save_scores().expect("Failed to save scores");
300 self.layout(cx);
301 }
302 _ => {
303 if let Some(game) = &mut self.game {
304 game.input(cx, key);
305 }
306 }
307 }
308 }
309
310 fn update_score(&mut self, cx: CX![]) {
311 if let Some(game) = &mut self.game {
312 let dur = game.score(cx);
313 let score = dur.as_secs() as i32;
314 let to_wait = if Some(score) != self.score {
315 self.score = Some(score);
316 self.draw_scores(cx);
317
318 // Update 100ms after next change
319 1_000_000_000 - dur.subsec_nanos() + 100_000_000
320 } else {
321 1_000_000_000
322 };
323 after!(Duration::from_nanos(to_wait as u64), [cx], update_score());
324 }
325 }
326
327 fn game_finished(&mut self, cx: CX![], dur: Duration) {
328 let score = dur.as_secs() as i32;
329 self.scores.push(score);
330 self.scores.sort_unstable();
331 self.save_scores().expect("Failed to save scores");
332 self.last_score = Some(score);
333 self.score = None;
334 self.game = None;
335 self.layout(cx);
336 }
337
338 fn score_path() -> PathBuf {
339 let mut path = std::env::home_dir().unwrap_or_default();
340 path.push(".teeclub-scores");
341 path
342 }
343
344 fn load_scores(&mut self) -> Option<()> {
345 let data = std::fs::read_to_string(Self::score_path()).ok()?;
346 let mut it = data.split(char::is_whitespace);
347 while let Some(tok) = it.next() {
348 match tok {
349 "adjust:" => {
350 self.adjust = it.next()?.parse::<i32>().ok()?;
351 }
352 "scores[" => {
353 self.scores = Vec::new();
354 loop {
355 match it.next()? {
356 "]" => break,
357 v => self.scores.push(v.parse::<i32>().ok()?),
358 }
359 }
360 self.scores.sort_unstable();
361 }
362 "" => (),
363 _ => return None,
364 }
365 }
366 Some(())
367 }
368
369 fn save_scores(&mut self) -> std::io::Result<()> {
370 let mut out = Vec::new();
371 let _ = writeln!(out, "adjust: {}", self.adjust);
372 let _ = writeln!(out, "scores[");
373 for sc in self.scores.iter().take(200) {
374 let _ = writeln!(out, "{sc}");
375 }
376 let _ = writeln!(out, "]");
377 std::fs::write(Self::score_path(), out)
378 }
379}
380
381/// Game state
382#[derive(Eq, PartialEq)]
383struct State {
384 pile: [Hand; 9],
385 stack: [Hand; 9],
386}
387
388impl State {
389 fn pack(&self) -> Vec<u8> {
390 let mut out = Vec::new();
391 for p in &self.pile {
392 p.pack(&mut out);
393 }
394 for s in &self.stack {
395 s.pack(&mut out);
396 }
397 out
398 }
399
400 fn unpack(mut data: &[u8]) -> Self {
401 let mut pile: [Hand; 9] = Default::default();
402 let mut stack: [Hand; 9] = Default::default();
403 for p in &mut pile {
404 *p = Hand::unpack(&mut data);
405 }
406 for s in &mut stack {
407 *s = Hand::unpack(&mut data);
408 }
409 Self { pile, stack }
410 }
411}
412
413/// Game state and gameplay handling
414struct Game {
415 tile: Tile,
416 history: Vec<Vec<u8>>,
417 state: State,
418 select: Option<(usize, usize)>, // (index, count)
419 dur: Duration,
420 activity: Instant,
421 ret: Option<Ret<Duration>>, // Report score for game completion
422}
423
424impl Game {
425 fn new(start: Instant, ret: Ret<Duration>) -> Self {
426 let seed = std::time::SystemTime::UNIX_EPOCH
427 .elapsed()
428 .map(|d| d.as_nanos() as u64)
429 .unwrap_or(0);
430
431 let mut rand = Rand32::new(seed);
432 let mut pile: [Hand; 9] = Default::default();
433 let mut stack: [Hand; 9] = Default::default();
434
435 let p0 = &mut pile[0];
436 p0.add_deck();
437 p0.add_deck();
438 p0.shuffle(&mut rand);
439 p0.shuffle(&mut rand);
440
441 for _ in 0..5 {
442 for s in &mut stack {
443 if let Some(card) = p0.pick_last() {
444 s.add(card);
445 }
446 }
447 }
448
449 Self {
450 tile: Tile::default(),
451 history: Vec::new(),
452 state: State { pile, stack },
453 select: None,
454 dur: Duration::from_secs(0),
455 activity: start,
456 ret: Some(ret),
457 }
458 }
459
460 fn select(&mut self, core: &mut Core, i: usize) {
461 let s = &self.state.stack[i];
462 let len = s.len();
463 if len == 0 {
464 self.select = None;
465 } else {
466 let mut count = 1;
467 while count < len && s[len - count].inc == Some(s[len - count - 1]) {
468 count += 1;
469 }
470 self.select = Some((i, count));
471 }
472 self.draw(core);
473 }
474
475 fn sel_inc(&mut self, core: &mut Core) {
476 if let Some((i, count)) = self.select {
477 let s = &self.state.stack[i];
478 let len = s.len();
479 if count < len && s[len - count].inc == Some(s[len - count - 1]) {
480 self.select = Some((i, count + 1));
481 self.draw(core);
482 }
483 }
484 }
485
486 fn sel_dec(&mut self, core: &mut Core) {
487 if let Some(sel) = &mut self.select {
488 let i = sel.0;
489 let count = sel.1;
490 if count <= 1 {
491 self.select = None;
492 } else {
493 self.select = Some((i, count - 1));
494 }
495 self.draw(core);
496 }
497 }
498
499 /// Mark this state by saving it in history, available to undo
500 /// later
501 fn mark(&mut self) {
502 let v = self.state.pack();
503 if let Some(last) = self.history.last()
504 && *last == v
505 {
506 return;
507 }
508 self.history.push(v);
509 }
510
511 /// Restore most recent marked state and remove it from history
512 fn undo(&mut self, core: &mut Core) {
513 if let Some(data) = self.history.pop() {
514 self.state = State::unpack(&data);
515 self.select = None;
516 self.draw(core);
517 }
518 }
519
520 /// Move selected cards to the given stack or to top if the stack
521 /// is the selected stack
522 fn move_to(&mut self, core: &mut Core, to: usize) {
523 let Some((fr, cnt)) = self.select else {
524 return;
525 };
526 self.select = None;
527 self.mark();
528
529 let s = &mut self.state;
530 let Some(fr_last) = s.stack[fr].peek_last() else {
531 return;
532 };
533
534 if fr == to {
535 // Move to top
536 for pi in (1..9).rev() {
537 let do_move = if let Some(to_last) = s.pile[pi].peek_last() {
538 to_last.inc == Some(fr_last)
539 } else {
540 fr_last.num == 1
541 };
542 if do_move {
543 for _ in 0..cnt {
544 if let Some(card) = s.stack[fr].pick_last() {
545 s.pile[pi].add(card);
546 }
547 }
548 break;
549 }
550 }
551 if self.is_finished()
552 && let Some(ret) = self.ret.take()
553 {
554 ret!([ret], self.score(core));
555 }
556 } else {
557 // Move to another pile
558 let mut copy_cnt = cnt;
559 if let Some(to_last) = s.stack[to].peek_last() {
560 copy_cnt = copy_cnt.min(to_last.num.saturating_sub(fr_last.num) as usize);
561 if to_last.num != fr_last.num + copy_cnt as u8 {
562 copy_cnt = 0;
563 }
564 }
565
566 let mut tmp = Vec::new();
567 for _ in 0..copy_cnt {
568 tmp.push(s.stack[fr].pick_last().unwrap());
569 }
570 while let Some(card) = tmp.pop() {
571 s.stack[to].add(card);
572 }
573 }
574 self.draw(core);
575 }
576
577 /// Move card down from stock pile
578 fn move_down(&mut self, core: &mut Core) {
579 self.mark();
580 if let Some(card) = self.state.pile[0].pick_last() {
581 self.state.stack[0].add(card);
582 self.select = None;
583 self.draw(core);
584 }
585 }
586
587 fn is_finished(&mut self) -> bool {
588 for i in 1..9 {
589 if self.state.pile[i].len() != 13 {
590 return false;
591 }
592 }
593 true
594 }
595
596 /// Set the tile to use for drawing, and redraw
597 fn layout(&mut self, core: &mut Core, tile: Tile) {
598 self.tile = tile;
599 self.draw(core);
600 }
601
602 /// Draw the playing area
603 fn draw(&mut self, core: &mut Core) {
604 if let Some(mut r) = self.tile.full(core) {
605 r.clear_all_99();
606
607 for col in 0..9 {
608 let x = 1 + col as i32 * 8;
609
610 // Pile
611 if let Some(card) = self.state.pile[col].peek_last() {
612 if col == 0 {
613 // Facedown pile
614 draw::card_back(&mut r, 0, x);
615 } else {
616 draw::card(&mut r, 0, x, card);
617 }
618 } else {
619 draw::card_space(&mut r, 0, x);
620 }
621
622 // Index number
623 r.hfb(7)
624 .at(CARD_SY, x)
625 .hfb(70)
626 .char((49 + col as u8) as char);
627
628 // Stack
629 let y0 = CARD_SY + 1;
630 let y1 = r.sy();
631 let hand = &self.state.stack[col];
632 draw::card_space(&mut r, y0, x);
633
634 let selected = self
635 .select
636 .map(|(index, count)| if col == index { count } else { 0 })
637 .unwrap_or(0);
638 let len = hand.len();
639 let sel_i = len.saturating_sub(selected);
640 let space = y1 - y0 - 1;
641 let mut y = y0;
642 let mut i = 0;
643 if len as i32 + CARD_SY > space {
644 draw::card_dots(&mut r, y, x);
645 y += 1;
646 i = len - (space - CARD_SY) as usize;
647 }
648 while i < hand.len() {
649 let card = hand[i];
650 if i >= sel_i {
651 draw::card(&mut r, y + 1, x + 1, card);
652 } else {
653 draw::card(&mut r, y, x, card);
654 }
655 i += 1;
656 y += 1;
657 }
658 }
659 }
660 }More examples
97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }168 fn draw_cells(&mut self, cx: CX![]) {
169 if let Some(mut full) = self.cells_tile.full(cx) {
170 let sx = full.sx();
171 full.clear_all_99();
172
173 let font_sx = self.font.sx;
174 let font_sy = self.font.sy;
175 let cell_wid = font_sx * 2;
176 let r = full.region(
177 0,
178 (sx - cell_wid as i32) / 2,
179 font_sy as i32,
180 cell_wid as i32,
181 );
182 draw_cell(
183 &self.font,
184 r,
185 self.index,
186 171,
187 7,
188 Some((self.cy, self.cx, self.mode)),
189 );
190 let max = self.font.index_limit();
191 for i in 1..=self.index {
192 let r = full.region(
193 0,
194 (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195 font_sy as i32,
196 cell_wid as i32,
197 );
198 if !r.is_visible() {
199 break;
200 }
201 draw_cell(&self.font, r, self.index - i, 70, 6, None);
202 }
203 for i in 1..(max - self.index) {
204 let r = full.region(
205 0,
206 (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207 font_sy as i32,
208 cell_wid as i32,
209 );
210 if !r.is_visible() {
211 break;
212 }
213 draw_cell(&self.font, r, self.index + i, 70, 6, None);
214 }
215 }
216 }
217
218 fn draw_mini(&mut self, cx: CX![]) {
219 if let Some(mut full) = self.mini_tile.full(cx) {
220 full.clear_all_99();
221 let sx = full.sx() * 2;
222 let sy = full.sy() * 4;
223 let font_sx = self.font.sx as i32;
224 let font_sy = self.font.sy as i32;
225 let oy = sy.saturating_sub(font_sy) / 2;
226 let mut ox = (sx - font_sx) / 2;
227 let mut index = self.index;
228 while ox > 0 && index > 0 {
229 ox -= font_sx;
230 index -= 1;
231 }
232 while ox < sx {
233 draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234 ox += font_sx;
235 index += 1;
236 }
237 }
238 }Sourcepub fn clear_all(&mut self) -> &mut Self
pub fn clear_all(&mut self) -> &mut Self
Clear the whole region to space characters of the current
hfb colour-pair. This will be clipped according to the
current and parent regions. The write position is set to
top-left, and the current colour is set to hfb.
Examples found in repository?
9pub fn card(r: &mut Region, y: i32, x: i32, card: Card) {
10 let mut r = r.region(y, x, CARD_SY, CARD_SX);
11 r.hfb(card.hfb).clear_all();
12 r.at(0, 2);
13 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
14 r.at(CARD_SY - 1, 2);
15 write!(r, "{}{}", card.num_ch, card.suit_ch).unwrap();
16}
17
18/// Draw the back of a card
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20 let mut r = r.region(y, x, CARD_SY, CARD_SX);
21 r.hfb(27);
22 for y in 0..CARD_SY {
23 r.at(y, 0).text("||||||");
24 }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29 let mut r = r.region(y, x, CARD_SY, CARD_SX);
30 r.hfb(71).clear_all();
31}More examples
172 fn draw_scores(&mut self, cx: CX![]) {
173 let mut score = self.score;
174 let mut last_score = self.last_score;
175 if let Some(mut r) = self.scores_tile.full(cx) {
176 r.hfb(176).clear_all();
177 r.hfb(7).text("SCORES");
178 let mut y = 1;
179 let last_y = r.sy() - 1;
180 let mut draw = move |hfb, sc| {
181 if y > 0 {
182 r.at(y, 0).hfb(hfb);
183 write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184 y += 1;
185 }
186 };
187
188 for &s0 in &self.scores {
189 if let Some(curr) = score
190 && (curr < s0 || y == last_y)
191 {
192 draw(7, curr);
193 score = None;
194 }
195 if Some(s0) == last_score {
196 draw(173, s0);
197 last_score = None;
198 } else {
199 draw(176, s0);
200 }
201 }
202 if let Some(s1) = score {
203 draw(173, s1);
204 }
205 }
206 }85 fn redraw(&mut self, cx: CX![]) {
86 if let Some(ref tsh) = self.tsh {
87 let out = tsh.output(cx);
88 out.attr_99().cursor_show().scroll_up().save_cleanup();
89 out.cursor_hide().clear_all_99_and_remote();
90
91 // Draw fixed stuff here, then break out and store
92 // sub-tiles for areas that need to be updated later
93 let tile = tsh.tile(cx);
94 let sx = tile.sx();
95 let sy = tile.sy();
96 let split_y1 = sy - self.font.sy as i32;
97 let split_y0 = split_y1.min(10);
98 let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99 let spare = sx.saturating_sub(80);
100 let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101 (spare / 2, 0)
102 } else {
103 (0, spare)
104 };
105 let (_, rest) = top.split_x(ox);
106 let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108 if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109 r.hfb(172).clear_all();
110 let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120 r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121 }
122 self.cells_tile = cells_tile;
123 self.param_tile = param_tile;
124 self.mini_tile = mini_tile;
125
126 self.draw_cells(cx);
127 self.draw_mini(cx);
128 self.draw_param(cx);
129 }
130 }
131
132 fn draw_param(&mut self, cx: CX![]) {
133 if let Some(mut r) = self.param_tile.full(cx) {
134 r.hfb(172).clear_all();
135
136 // Use sub-region to clip the filename if it's too long
137 r.region(0, 0, 1, 39)
138 .hfb(172)
139 .text("File: ")
140 .text(&self.font.path.to_string_lossy());
141
142 let count = self.font.index_limit();
143 let index = self.index;
144
145 let state = if let Some(ref err) = self.save_error {
146 err
147 } else if self.unsaved {
148 "UNSAVED"
149 } else {
150 ""
151 };
152 let cp = self.font.chars[index].cp;
153 let ch = match cp {
154 32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155 _ => '?',
156 };
157 let sx = self.font.sx;
158 let sy = self.font.sy;
159
160 r.at(1, 0);
161 let _ = writeln!(r, "Size: {sx} x {sy}, {count} glyphs");
162 let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163 let _ = writeln!(r, "Mode: {}", self.mode.caps());
164 let _ = writeln!(r, "State: {}", state);
165 }
166 }Sourcepub fn clear_eol_99(&mut self) -> &mut Self
pub fn clear_eol_99(&mut self) -> &mut Self
Clear to end-of-line with the default colour-pair. Leaves the current colour-pair set to 99.
Sourcepub fn text(&mut self, text: &str) -> &mut Self
pub fn text(&mut self, text: &str) -> &mut Self
Write some text at the current location with the current HFB,
wrapping to the start of the next line at each row-end of the
region. What is actually output to the page will be clipped
according to all current and parent regions. Interprets
\0ZZZ colour sequences to change the current HFB.
Interprets \n.
Note that even if the text is partially or fully outside the clip region, the write position will still be advanced correctly.
Examples found in repository?
19pub fn card_back(r: &mut Region, y: i32, x: i32) {
20 let mut r = r.region(y, x, CARD_SY, CARD_SX);
21 r.hfb(27);
22 for y in 0..CARD_SY {
23 r.at(y, 0).text("||||||");
24 }
25}
26
27/// Draw the space where a card could go
28pub fn card_space(r: &mut Region, y: i32, x: i32) {
29 let mut r = r.region(y, x, CARD_SY, CARD_SX);
30 r.hfb(71).clear_all();
31}
32
33/// Draw "more cards" indicator
34pub fn card_dots(r: &mut Region, y: i32, x: i32) {
35 let mut r = r.region(y, x, CARD_SY, CARD_SX);
36 r.hfb(70);
37 for y in 0..CARD_SY {
38 r.at(y, 0).text("::::::");
39 }
40}More examples
172 fn draw_scores(&mut self, cx: CX![]) {
173 let mut score = self.score;
174 let mut last_score = self.last_score;
175 if let Some(mut r) = self.scores_tile.full(cx) {
176 r.hfb(176).clear_all();
177 r.hfb(7).text("SCORES");
178 let mut y = 1;
179 let last_y = r.sy() - 1;
180 let mut draw = move |hfb, sc| {
181 if y > 0 {
182 r.at(y, 0).hfb(hfb);
183 write!(r, "{:3}:{:02}", sc / 60, sc % 60).unwrap();
184 y += 1;
185 }
186 };
187
188 for &s0 in &self.scores {
189 if let Some(curr) = score
190 && (curr < s0 || y == last_y)
191 {
192 draw(7, curr);
193 score = None;
194 }
195 if Some(s0) == last_score {
196 draw(173, s0);
197 last_score = None;
198 } else {
199 draw(176, s0);
200 }
201 }
202 if let Some(s1) = score {
203 draw(173, s1);
204 }
205 }
206 }
207
208 fn draw_logo(&self, mut r: Region<'_>) {
209 const LOGO_HFB: [u16; 30] = [
210 160, 160, 160, 140, 140, 140, 140, 140, 150, 150, 150, 150, 150, 110, 110, 110, 110,
211 110, 130, 130, 130, 130, 130, 120, 120, 120, 120, 120, 160, 160,
212 ];
213 const LOGO: [&str; 9] = [
214 r" ___________________________________ ",
215 r" / ____ ____________________________\ ",
216 r"/ / / / __ __ __ ",
217 r"\ \ / / ___ ___ _____/ /_ __/ /_ \ \ ",
218 r" \/ / / / _ \/ _ \/ ___/ / / / / __ \ \ \ ",
219 r" / / / __/ __/ /__/ / /_/ / /_/ / \ \ ",
220 r" \_\ \___/\___/\___/_/\__,_/_.___/ /_ \ \",
221 r" ___________________________________/ /",
222 r" \___________________________________/ ",
223 ];
224 let logo_hfb = match self.logo_hfb.as_ref() {
225 Some(v) => v.as_slice(),
226 None => LOGO_HFB.as_slice(),
227 };
228 let ox = (r.sx() - 43) / 2;
229 let oy = (r.sy() - 9) / 2;
230 for (y, s) in LOGO.iter().enumerate() {
231 r.at(oy + y as i32, ox);
232 for (x, c) in s.chars().enumerate() {
233 r.hfb(logo_hfb[(10 + x + y) % 30]);
234 r.char(c);
235 }
236 }
237 }
238
239 fn draw_instructions(mut r: Region<'_>) {
240 const TEXT: &str = r"
241
242 Keys: [N] New game; [Q] Quit; [1] to [9] Select cards in
243 corresponding column; [+]/[=] or [-] Increase or decrease
244 number of selected cards; [1] to [9] If the same digit as the
245 selecting keypress, move selected cards to top, if another
246 digit, move as many of selected cards as possible to that
247 column; [Space] Bring a new card down from the stock pile;
248 [BackSp] Go back one move; [<]/[>] Adjust display left/right.
249
250 Rules: The aim is to move all the cards to the top area, the
251 spaces the the right of the stock pile. Each top pile must
252 consist of one suit only, stacked in order from Ace up to
253 King. The cards in the main area can be moved around:
254 sequences of one or more cards of the same suit may be moved
255 on top of a card with the next-higher number, of any suit.
256 The bulk of the cards are in the stock pile in the top-left.
257 Cards may be brought down from there using Space.
258
259 ";
260 let lines: Vec<_> = TEXT.trim().split('\n').map(|s| s.to_string()).collect();
261 let n_lines = lines.len();
262 let oy = (r.sy() - n_lines as i32) / 2;
263 for (i, line) in lines.iter().enumerate() {
264 r.at(i as i32 + oy, 7);
265 let line = line.trim().replace("[", "\0171 ").replace("]", " \0099");
266 r.text(&line);
267 }
268 }97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }85 fn redraw(&mut self, cx: CX![]) {
86 if let Some(ref tsh) = self.tsh {
87 let out = tsh.output(cx);
88 out.attr_99().cursor_show().scroll_up().save_cleanup();
89 out.cursor_hide().clear_all_99_and_remote();
90
91 // Draw fixed stuff here, then break out and store
92 // sub-tiles for areas that need to be updated later
93 let tile = tsh.tile(cx);
94 let sx = tile.sx();
95 let sy = tile.sy();
96 let split_y1 = sy - self.font.sy as i32;
97 let split_y0 = split_y1.min(10);
98 let (top, _, cells_tile) = tile.split_yy(split_y0, split_y1);
99 let spare = sx.saturating_sub(80);
100 let (ox, mini_sx) = if (spare as usize) < self.font.sx / 2 {
101 (spare / 2, 0)
102 } else {
103 (0, spare)
104 };
105 let (_, rest) = top.split_x(ox);
106 let (param_tile, mini_tile, mut keys_tile) = rest.split_xx(40, 40 + mini_sx);
107
108 if let Some(mut r) = keys_tile.region(cx, 0, 0, split_y0, 40) {
109 r.hfb(172).clear_all();
110 let text = "\
111Keys: Arrows move, [Space] change pixel
112Mode on/off: [d] draw, [r] roll
113Fill: [f] flood, [v] vert, [h] horiz
114[n] new glyph, [u] undo, [l] ruler line
115Clip: [c] copy, [P] paste.
116Index: [PgUp] [PgDn] [Home] [End].
117[S] save, [Q] save+quit, [^C] hard quit
118Edit the font file directly to change
119font size or codepoint numbers.";
120 r.text(&text.replace('[', "\0171 ").replace(']', " \0172"));
121 }
122 self.cells_tile = cells_tile;
123 self.param_tile = param_tile;
124 self.mini_tile = mini_tile;
125
126 self.draw_cells(cx);
127 self.draw_mini(cx);
128 self.draw_param(cx);
129 }
130 }
131
132 fn draw_param(&mut self, cx: CX![]) {
133 if let Some(mut r) = self.param_tile.full(cx) {
134 r.hfb(172).clear_all();
135
136 // Use sub-region to clip the filename if it's too long
137 r.region(0, 0, 1, 39)
138 .hfb(172)
139 .text("File: ")
140 .text(&self.font.path.to_string_lossy());
141
142 let count = self.font.index_limit();
143 let index = self.index;
144
145 let state = if let Some(ref err) = self.save_error {
146 err
147 } else if self.unsaved {
148 "UNSAVED"
149 } else {
150 ""
151 };
152 let cp = self.font.chars[index].cp;
153 let ch = match cp {
154 32..=126 | 160.. => char::from_u32(cp).unwrap_or('?'),
155 _ => '?',
156 };
157 let sx = self.font.sx;
158 let sy = self.font.sy;
159
160 r.at(1, 0);
161 let _ = writeln!(r, "Size: {sx} x {sy}, {count} glyphs");
162 let _ = writeln!(r, "Index: {index} U+{cp:04x} '{ch}'");
163 let _ = writeln!(r, "Mode: {}", self.mode.caps());
164 let _ = writeln!(r, "State: {}", state);
165 }
166 }
167
168 fn draw_cells(&mut self, cx: CX![]) {
169 if let Some(mut full) = self.cells_tile.full(cx) {
170 let sx = full.sx();
171 full.clear_all_99();
172
173 let font_sx = self.font.sx;
174 let font_sy = self.font.sy;
175 let cell_wid = font_sx * 2;
176 let r = full.region(
177 0,
178 (sx - cell_wid as i32) / 2,
179 font_sy as i32,
180 cell_wid as i32,
181 );
182 draw_cell(
183 &self.font,
184 r,
185 self.index,
186 171,
187 7,
188 Some((self.cy, self.cx, self.mode)),
189 );
190 let max = self.font.index_limit();
191 for i in 1..=self.index {
192 let r = full.region(
193 0,
194 (sx - (cell_wid * (1 + i * 2)) as i32) / 2,
195 font_sy as i32,
196 cell_wid as i32,
197 );
198 if !r.is_visible() {
199 break;
200 }
201 draw_cell(&self.font, r, self.index - i, 70, 6, None);
202 }
203 for i in 1..(max - self.index) {
204 let r = full.region(
205 0,
206 (sx + (cell_wid * (i * 2 - 1)) as i32) / 2,
207 font_sy as i32,
208 cell_wid as i32,
209 );
210 if !r.is_visible() {
211 break;
212 }
213 draw_cell(&self.font, r, self.index + i, 70, 6, None);
214 }
215 }
216 }
217
218 fn draw_mini(&mut self, cx: CX![]) {
219 if let Some(mut full) = self.mini_tile.full(cx) {
220 full.clear_all_99();
221 let sx = full.sx() * 2;
222 let sy = full.sy() * 4;
223 let font_sx = self.font.sx as i32;
224 let font_sy = self.font.sy as i32;
225 let oy = sy.saturating_sub(font_sy) / 2;
226 let mut ox = (sx - font_sx) / 2;
227 let mut index = self.index;
228 while ox > 0 && index > 0 {
229 ox -= font_sx;
230 index -= 1;
231 }
232 while ox < sx {
233 draw_mini_cell(&self.font, full.full(), oy, ox, index, 170);
234 ox += font_sx;
235 index += 1;
236 }
237 }
238 }
239
240 fn draw_all(&mut self, cx: CX![]) {
241 self.draw_cells(cx);
242 self.draw_mini(cx);
243 self.draw_param(cx);
244 }
245
246 fn input(&mut self, cx: CX![], key: Key) {
247 match key {
248 Key::Ctrl('L') => self.redraw(cx),
249 Key::Ctrl('C') => stop!(cx),
250 Key::Left => {
251 if self.cx > 0 {
252 self.move_or_draw(cx, 0, -1)
253 }
254 }
255 Key::Right => {
256 if self.cx + 1 < self.font.sx {
257 self.move_or_draw(cx, 0, 1)
258 }
259 }
260 Key::Up => {
261 if self.cy > 0 {
262 self.move_or_draw(cx, -1, 0)
263 }
264 }
265 Key::Down => {
266 if self.cy + 1 < self.font.sy {
267 self.move_or_draw(cx, 1, 0)
268 }
269 }
270 Key::PgUp => {
271 if self.index > 0 {
272 self.index -= 1;
273 self.draw_all(cx);
274 }
275 }
276 Key::PgDn => {
277 if self.index + 1 < self.font.index_limit() {
278 self.index += 1;
279 self.draw_all(cx);
280 }
281 }
282 Key::Home => {
283 self.index = 0;
284 self.draw_all(cx);
285 }
286 Key::End => {
287 self.index = self.font.index_limit() - 1;
288 self.draw_all(cx);
289 }
290 Key::Pr(' ') => {
291 self.undo_push();
292 self.unsaved = true;
293 let curr = self.font.get(self.index, self.cy, self.cx);
294 self.font.set(self.index, self.cy, self.cx, !curr);
295 self.draw_all(cx);
296 }
297 Key::Pr('d') => {
298 self.mode = match self.mode {
299 Mode::Draw => Mode::Move,
300 _ => Mode::Draw,
301 };
302 self.draw_all(cx);
303 }
304 Key::Pr('r') => {
305 self.mode = match self.mode {
306 Mode::Roll => Mode::Move,
307 _ => Mode::Roll,
308 };
309 self.draw_all(cx);
310 }
311 Key::Pr('f') => {
312 self.undo_push();
313 self.unsaved = true;
314 self.font.fill(self.index, self.cy, self.cx, true, true);
315 self.draw_all(cx);
316 }
317 Key::Pr('h') => {
318 self.undo_push();
319 self.unsaved = true;
320 self.font.fill(self.index, self.cy, self.cx, false, true);
321 self.draw_all(cx);
322 }
323 Key::Pr('v') => {
324 self.undo_push();
325 self.unsaved = true;
326 self.font.fill(self.index, self.cy, self.cx, true, false);
327 self.draw_all(cx);
328 }
329 Key::Pr('u') => {
330 if let Some(font) = self.undo.pop_front() {
331 self.font = font;
332 let limit = self.font.index_limit();
333 self.index = self.index.min(limit - 1);
334 self.draw_all(cx);
335 }
336 }
337 Key::Pr('l') => {
338 self.font.rule_bm ^= 1 << self.cy;
339 self.unsaved = true;
340 self.draw_all(cx);
341 }
342 Key::Pr('n') => {
343 self.undo_push();
344 self.unsaved = true;
345 self.index = self.font.index_limit();
346 self.font.add_glyph();
347 self.draw_param(cx);
348 self.draw_cells(cx);
349 }
350 Key::Pr('c') => {
351 self.clip = self.font.get_glyph(self.index);
352 }
353 Key::Pr('P') => {
354 if !self.clip.is_empty() {
355 self.undo_push();
356 self.unsaved = true;
357 self.font.set_glyph(self.index, &self.clip);
358 self.draw_all(cx);
359 }
360 }
361 Key::Pr('S') => {
362 if let Err(e) = self.font.save() {
363 self.save_error = Some(e.to_string());
364 } else {
365 self.save_error = None;
366 self.unsaved = false;
367 }
368 self.draw_param(cx);
369 }
370 Key::Pr('Q') => {
371 if let Err(e) = self.font.save() {
372 fail!(cx, e);
373 } else {
374 stop!(cx);
375 }
376 }
377 _ => call!([self.term], bell()),
378 }
379 }
380
381 fn move_or_draw(&mut self, cx: CX![], dy: i32, dx: i32) {
382 match self.mode {
383 Mode::Move => {
384 self.cx = (self.cx as i32 + dx) as usize;
385 self.cy = (self.cy as i32 + dy) as usize;
386 self.draw_cells(cx);
387 }
388 Mode::Draw => {
389 self.undo_push();
390 self.unsaved = true;
391 let curr = self.font.get(self.index, self.cy, self.cx);
392 self.cx = (self.cx as i32 + dx) as usize;
393 self.cy = (self.cy as i32 + dy) as usize;
394 self.font.set(self.index, self.cy, self.cx, curr);
395 self.draw_all(cx);
396 }
397 Mode::Roll => {
398 self.undo_push();
399 if dx != 0 {
400 self.font.roll_horiz(self.index, dx);
401 }
402 if dy != 0 {
403 self.font.roll_vert(self.index, dy);
404 }
405 self.draw_all(cx);
406 }
407 }
408 }
409
410 fn undo_push(&mut self) {
411 self.undo.push_front(self.font.clone());
412 while self.undo.len() > 50 {
413 self.undo.pop_back();
414 }
415 }
416}
417
418fn draw_cell(
419 font: &Font,
420 mut r: Region,
421 index: usize,
422 black: u16,
423 white: u16,
424 curs: Option<(usize, usize, Mode)>,
425) {
426 let form = &font.chars[index].form;
427 for y in 0..font.sy {
428 let rule = ((font.rule_bm >> y) & 1) != 0;
429 for x in 0..font.sx {
430 r.hfb(if form[x + y * font.sx] { white } else { black });
431 let mut text = if rule { "--" } else { " " };
432 if let Some((cy, cx, mode)) = curs
433 && cy == y
434 && cx == x
435 {
436 text = mode.cursor();
437 }
438 r.text(text);
439 }
440 }
441}Sourcepub fn bytes(&mut self, text: &[u8]) -> &mut Self
pub fn bytes(&mut self, text: &[u8]) -> &mut Self
Write some text (expressed as bytes of UTF-8) at the current
location with the current HFB, wrapping to the start of the
next line at each row-end of the region. What is actually
output to the page will be clipped according to all current
and parent regions. Interprets \0ZZZ colour sequences to
change the current HFB. Interprets \n.
Note that even if the text is partially or fully outside the clip region, the write position will still be advanced correctly.
Sourcepub fn get(&mut self, y: i32, x: i32) -> Option<(u16, &[u8])>
pub fn get(&mut self, y: i32, x: i32) -> Option<(u16, &[u8])>
Get the contents of the cell at the given location relative to
the region’s top-left. Returns None if the location is
outside the region’s clip. Otherwise returns Some((hfb, data)) where data is the UTF-8 data of the cell. Note that
in the case of a double-width character, the data will be
prefixed with an 0xFF byte in the left cell, and will contain
an 0xFE byte alone in the right cell.
Sourcepub fn set_hfb(&mut self, y: i32, x: i32, hfb: u16)
pub fn set_hfb(&mut self, y: i32, x: i32, hfb: u16)
Set the HFB colour of a cell without affecting the glyph. Location is relative to region top-left.
Examples found in repository?
97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }Sourcepub fn set(&mut self, y: i32, x: i32, hfb: u16, data: &[u8])
pub fn set(&mut self, y: i32, x: i32, hfb: u16, data: &[u8])
Set the HFB colour and UTF-8 glyph data of a cell. Location is relative to region top-left. For double-width glyphs, the left cell should contain byte 0xFF plus the UTF-8, and the right cell should contain only an 0xFE byte. If one or other half is missing, the incorrect cells will show as the replacement character.
Sourcepub fn braille_plot(&mut self, py: i32, px: i32, hfb: u16, set: bool)
pub fn braille_plot(&mut self, py: i32, px: i32, hfb: u16, set: bool)
Plot or unplot a point using Braille glyphs. Braille glyphs
contain 2 points across and 4 down. They can be used for
lo-res graphics. The total grid available to be addressed by
(py, px) is 4*sy high and 2*sx wide. The only restriction
is that the HFB colour-pair cannot be set independently for
each point. Those apply at the glyph level. On plotting, if
the point being plotted belongs to a non-Braille glyph, then
that glyph is overwritten. If it is a Braille glyph then the
point is added to it (set == true) or removed from it (set == false).
Examples found in repository?
443fn draw_mini_cell(font: &Font, mut r: Region, oy: i32, ox: i32, index: usize, hfb: u16) {
444 if index < font.chars.len() {
445 let form = &font.chars[index].form;
446 for y in 0..font.sy {
447 for x in 0..font.sx {
448 if form[x + y * font.sx] {
449 r.braille_plot(oy + y as i32, ox + x as i32, hfb, true);
450 }
451 }
452 }
453 }
454}Trait Implementations§
Source§impl Write for Region<'_>
impl Write for Region<'_>
Source§fn flush(&mut self) -> Result<()>
fn flush(&mut self) -> Result<()>
The data is considered already flushed as soon as it is written to the region.
Source§fn write(&mut self, buf: &[u8]) -> Result<usize>
fn write(&mut self, buf: &[u8]) -> Result<usize>
Source§fn is_write_vectored(&self) -> bool
fn is_write_vectored(&self) -> bool
can_vector)1.0.0 · Source§fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
Source§fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
write_all_vectored)