extern crate pancurses; pub use pancurses::Input::*; pub use pancurses::*; use crate::game::*; pub struct GameLog { action_window: Window, pieces_window: Window, has_colours: bool, white_colour: u8, black_colour: u8, } impl GameLog { pub fn new( ypos: i32, xpos: i32, height: i32, width: i32, has_colours: bool, white_colour: u8, black_colour: u8, ) -> GameLog { GameLog { pieces_window: newwin(4, width, ypos, xpos), action_window: newwin(height, width, ypos + 5, xpos), has_colours: has_colours, white_colour: white_colour, black_colour: black_colour, } } pub fn update(&self, game: &Game) { // No need to clear, always stays the same size self.pieces_window.mv(0, 0); if self.has_colours { self.pieces_window.attrset(ColorPair(self.white_colour)); } self.pieces_window.addstr(format!( "W: {:2}", game.query_pieces(Player::White, Stone::Flat) )); self.pieces_window.attrset(Attribute::Normal); self.pieces_window.addstr(" "); if self.has_colours { self.pieces_window.attrset(ColorPair(self.black_colour)); } self.pieces_window.addstr(format!( "B: {:2}", game.query_pieces(Player::Black, Stone::Flat), )); self.pieces_window.attrset(Attribute::Normal); self.pieces_window.mv(1, 0); if self.has_colours { self.pieces_window.attrset(ColorPair(self.white_colour)); } self.pieces_window.addstr(format!( "WC: {}", game.query_pieces(Player::White, Stone::Capstone) )); self.pieces_window.attrset(Attribute::Normal); self.pieces_window.addstr(" "); if self.has_colours { self.pieces_window.attrset(ColorPair(self.black_colour)); } self.pieces_window.addstr(format!( "BC: {}", game.query_pieces(Player::Black, Stone::Capstone), )); self.pieces_window.attrset(Attribute::Normal); self.pieces_window.mv(3, 0); self.pieces_window.addstr("Player: "); match game.query_current_player() { Player::Black => { if self.has_colours { self.pieces_window.attrset(ColorPair(self.black_colour)); } self.pieces_window.addstr("Black"); } Player::White => { if self.has_colours { self.pieces_window.attrset(ColorPair(self.white_colour)); } self.pieces_window.addstr("White"); } } self.pieces_window.refresh(); // Perhaps it's best to avoid clearing things? Overwrite the specific characters instead? self.action_window.clear(); self.action_window.addstr(game.query_action_lines()); self.action_window.refresh(); } } pub struct BoardGUI { board_window: Window, cell_windows: Vec, size: u8, cell_height: u8, cell_width: u8, hoff: i32, cur_cell: Position, has_colours: bool, white_colour: u8, black_colour: u8, } pub enum BGAction { Quit, Selected(Action), } impl BoardGUI { fn draw_grid(&self) { let s: i32 = self.size as i32; let h: i32 = self.cell_height as i32; let w: i32 = self.cell_width as i32; for y in 0..h * s + 1 { for x in 0..w * s + 1 { if y % h == 0 { if x % w == 0 { self.board_window.mvaddch(y, x + self.hoff, '+'); } else { self.board_window.mvaddch(y, x + self.hoff, '-'); } } else if x % w == 0 { self.board_window.mvaddch(y, x + self.hoff, '|'); } } } for y in 0..s { self.board_window .mvaddstr(y * h + h / 2, 0, format!("{}.", s - y)); } for x in 0..s { self.board_window.mvaddstr( s * h + 1, w * x + w / 2 + self.hoff, format!("{:x}.", x + 10), ); } self.board_window.refresh(); } fn pos_to_idx(&self, pos: &Position) -> usize { let y = pos.y as usize; let x = pos.x as usize; x + y * (self.size as usize) } fn mv_to_cell(&self, pos: &Position, line: i32) { if let Some(win) = self.cell_windows.get(self.pos_to_idx(pos)) { win.mv(line, 0); win.refresh(); } } fn draw_stone(&self, win: &Window, top: bool, player: &Player, stone: &Stone) { if self.has_colours { match player { Player::Black => win.attrset(ColorPair(self.black_colour)), Player::White => win.attrset(ColorPair(self.white_colour)), }; if top { win.addch(match stone { Stone::Flat => '#', Stone::Standing => '/', Stone::Capstone => '*', }); } else { win.addstr(format!("{}", player)); } } else { win.addstr(format!("{}", player)); if top { win.addstr(format!("{}", stone)); } } } fn draw_stack(&self, pos: &Position, stack: &Stack) { if let Some(win) = self.cell_windows.get(self.pos_to_idx(pos)) { win.clear(); let height = stack.len() as usize; if height > 0 { let carry_capacity = self.size as usize; let tall = height > carry_capacity; if tall { win.attrset(Attribute::Normal); win.addch('('); } for i in 0..height { let piece = &stack[i]; self.draw_stone(win, i + 1 == height, &piece.player, &piece.stone); if tall && i + 2 == carry_capacity { win.attrset(Attribute::Normal); win.addch(')'); } } } win.refresh(); } } pub fn update(&self, game: &Game, squares: Vec) { // Is this really the `best' way? assert!(game.get_size() == self.size); for p in squares { if let Some(stack) = game.query_square(&p) { self.draw_stack(&p, stack); } } // Prevent redraw of the grid itself? self.board_window.untouch(); } pub fn read_action(&mut self) -> BGAction { loop { if let Some(key) = self.board_window.getch() { match key { KeyDown => { if 0 < self.cur_cell.y { self.cur_cell.y -= 1; self.mv_to_cell(&self.cur_cell, 0); } } KeyUp => { if self.cur_cell.y + 1 < self.size { self.cur_cell.y += 1; self.mv_to_cell(&self.cur_cell, 0); } } KeyLeft => { if 0 < self.cur_cell.x { self.cur_cell.x -= 1; self.mv_to_cell(&self.cur_cell, 0); } } KeyRight => { if self.cur_cell.x + 1 < self.size { self.cur_cell.x += 1; self.mv_to_cell(&self.cur_cell, 0); } } Character('q') => return BGAction::Quit, x => { self.board_window.addstr(format!("{:?}", x)); self.board_window.refresh(); } } } } } pub fn new( ypos: i32, xpos: i32, size: u8, cell_height: u8, cell_width: u8, hoff: u8, has_colours: bool, white_colour: u8, black_colour: u8, ) -> BoardGUI { let hoff = hoff as i32; let s: i32 = size as i32; let h: i32 = cell_height as i32; let w: i32 = cell_width as i32; let board_window = newwin(h * s + 3, w * s + 3 + hoff, ypos, xpos); let mut cell_windows: Vec = Vec::new(); for i in 0..s * s { let y: i32 = i / s; let x: i32 = i % s; cell_windows.push(newwin(h - 1, w - 1, (s - y - 1) * h + 1, hoff + x * w + 1)); } let bg = BoardGUI { board_window: board_window, cell_windows: cell_windows, size: size, cell_height: cell_height, cell_width: cell_width, hoff: hoff, cur_cell: Position { x: 0, y: 0 }, has_colours: has_colours, black_colour: black_colour, white_colour: white_colour, }; bg.board_window.keypad(true); bg.board_window.nodelay(false); bg.draw_grid(); // for i in 0..s * s { // let y: i32 = i / s; // let x: i32 = i % s; // let y = (s - y - 1) * h + 1; // let x = hoff + x * w + 1; // bg.cell_windows[i as usize].addstr(format!("{}:({},{})", i, y, x)); // bg.cell_windows[i as usize].refresh(); // } bg.mv_to_cell(&bg.cur_cell, 0); bg.board_window.refresh(); bg } } pub struct ActionEntry { window: Window, max_len: usize, xstart: i32, } impl ActionEntry { pub fn clear_entry_area(&self) { self.window.mv(0, self.xstart); for _i in 0..self.max_len { self.window.addch('_'); } self.window.mv(0, self.xstart); self.window.refresh(); } pub fn new(ypos: i32, xpos: i32, max_len: usize) -> ActionEntry { let txt = "Enter action: "; let xstart = txt.len() as i32; let width = max_len as i32 + xstart + 1; let ae = ActionEntry { window: newwin(1, width, ypos, xpos), max_len: max_len, xstart: xstart, }; ae.window.nodelay(false); ae.window.keypad(true); ae.window.addstr(txt); ae.clear_entry_area(); ae } pub fn read_action(&self) -> String { self.window.mv(0, self.xstart); self.window.refresh(); let mut result = String::new(); loop { if let Some(inp) = self.window.getch() { match inp { Character('\n') => return result, KeyEnter => return result, // TODO: is this ever used? Character(c) => { // Annoyingly there are two backspace possibilities let ascii = c as usize; if !result.is_empty() && (ascii == 127) || (ascii == 8) { result.pop(); let (y, x) = (self.window.get_cur_y(), self.window.get_cur_x()); self.window.mv(y, x - 1); self.window.addch('_'); self.window.mv(y, x - 1); } else { if result.len() < self.max_len { result.push(c); self.window.addch(c); } } } KeyBackspace => { if !result.is_empty() { result.pop(); let (y, x) = (self.window.get_cur_y(), self.window.get_cur_x()); self.window.mv(y, x - 1); self.window.addch('_'); self.window.mv(y, x - 1); } } _ => (), }; } self.window.refresh(); } } } pub struct MessageLog { window: Window, has_colours: bool, red_colour: u8, } impl MessageLog { pub fn new( ypos: i32, xpos: i32, height: i32, width: i32, has_colours: bool, red_colour: u8, ) -> MessageLog { let ml = MessageLog { window: newwin(height, width, ypos, xpos), has_colours: has_colours, red_colour: red_colour, }; ml } pub fn log_message(&self, str: String) { self.window.clear(); if self.has_colours { self.window.attrset(Attribute::Normal); } self.window.addstr(str); self.window.refresh(); } pub fn log_error(&self, str: String) { self.window.clear(); if self.has_colours { self.window.attrset(ColorPair(self.red_colour)); } self.window.addstr(str); self.window.refresh(); } }