Added basic game with random agent
parent
7239eb15d7
commit
2d7432c228
|
@ -0,0 +1,139 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "ops"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rand",
|
||||
"termion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417813675a504dfbbf21bfde32c03e5bf9f2413999962b479023c02848c1c7a5"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
"numtoa",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "ops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.80"
|
||||
rand = "0.8.5"
|
||||
termion = "3.0.0"
|
|
@ -0,0 +1,219 @@
|
|||
use std::fmt::Display;
|
||||
use termion::color;
|
||||
|
||||
use crate::{Player, Symb};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Token {
|
||||
Number(f32),
|
||||
OpAdd,
|
||||
OpSub,
|
||||
OpMult,
|
||||
OpDiv,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
fn val(&self) -> f32 {
|
||||
match self {
|
||||
Self::Number(x) => *x,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Board {
|
||||
board: [Option<(Symb, Player)>; 11],
|
||||
free_spots: usize,
|
||||
current_player: Player,
|
||||
}
|
||||
|
||||
impl Display for Board {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// Print board
|
||||
for i in &self.board {
|
||||
match i {
|
||||
Some((symb, player)) => write!(
|
||||
f,
|
||||
"{}{}{}",
|
||||
color::Fg(player.color()),
|
||||
symb,
|
||||
color::Fg(color::Reset)
|
||||
)?,
|
||||
|
||||
None => write!(f, "_")?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn new(current_player: Player) -> Self {
|
||||
Self {
|
||||
free_spots: 11,
|
||||
board: Default::default(),
|
||||
current_player,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_player(&self) -> Player {
|
||||
self.current_player
|
||||
}
|
||||
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.free_spots == 0
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.board.len()
|
||||
}
|
||||
|
||||
// Place the marked symbol at the given position.
|
||||
// Returns true for valid moves and false otherwise.
|
||||
pub fn play(&mut self, cursor: usize, symb: Symb) -> bool {
|
||||
match &self.board[cursor] {
|
||||
Some(_) => return false,
|
||||
None => {
|
||||
// Check for duplicate symbols
|
||||
for i in &self.board {
|
||||
if let Some(i) = i {
|
||||
if i.0 == symb {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check syntax
|
||||
match symb {
|
||||
Symb::Minus => {
|
||||
if cursor == self.board.len() - 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let r = &self.board[cursor + 1];
|
||||
if r.is_some_and(|(s, _)| s.is_binop()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Symb::Zero => {
|
||||
if cursor != 0 {
|
||||
let l = &self.board[cursor - 1].map(|x| x.0);
|
||||
if l == &Some(Symb::Div) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Symb::Div | Symb::Plus | Symb::Times => {
|
||||
if cursor == 0 || cursor == self.board.len() - 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let l = &self.board[cursor - 1].map(|x| x.0);
|
||||
let r = &self.board[cursor + 1].map(|x| x.0);
|
||||
|
||||
if symb == Symb::Div && r == &Some(Symb::Zero) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if l.is_some_and(|s| s.is_binop() || s.is_minus())
|
||||
|| r.is_some_and(|s| s.is_binop())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.board[cursor] = Some((symb, self.current_player));
|
||||
}
|
||||
}
|
||||
|
||||
self.current_player.invert();
|
||||
self.free_spots -= 1;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn evaluate(&self) -> Option<f32> {
|
||||
if !self.is_done() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
let mut is_neg = true; // if true, - is negative. if false, subtract.
|
||||
let mut current_num = 0f32;
|
||||
let mut current_sgn = 1f32;
|
||||
|
||||
for (s, _) in self.board.iter().map(|x| x.unwrap()) {
|
||||
match s {
|
||||
Symb::Div => {
|
||||
tokens.push(Token::Number(current_num * current_sgn));
|
||||
current_num = 0.0;
|
||||
current_sgn = 1.0;
|
||||
tokens.push(Token::OpDiv);
|
||||
is_neg = true;
|
||||
}
|
||||
Symb::Minus => {
|
||||
if is_neg {
|
||||
current_sgn = -1.0;
|
||||
} else {
|
||||
tokens.push(Token::Number(current_num * current_sgn));
|
||||
current_num = 0.0;
|
||||
current_sgn = 1.0;
|
||||
tokens.push(Token::OpSub);
|
||||
is_neg = true;
|
||||
}
|
||||
}
|
||||
Symb::Plus => {
|
||||
tokens.push(Token::Number(current_num * current_sgn));
|
||||
current_num = 0.0;
|
||||
current_sgn = 1.0;
|
||||
tokens.push(Token::OpAdd);
|
||||
is_neg = true;
|
||||
}
|
||||
Symb::Times => {
|
||||
tokens.push(Token::Number(current_num * current_sgn));
|
||||
current_num = 0.0;
|
||||
current_sgn = 1.0;
|
||||
tokens.push(Token::OpMult);
|
||||
is_neg = true;
|
||||
}
|
||||
Symb::Zero => {
|
||||
current_num = current_num * 10.0;
|
||||
is_neg = false;
|
||||
}
|
||||
Symb::Number(x) => {
|
||||
current_num = current_num * 10.0 + (x.get() as f32);
|
||||
is_neg = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push(Token::Number(current_num * current_sgn));
|
||||
|
||||
for op in [Token::OpMult, Token::OpDiv, Token::OpAdd, Token::OpSub] {
|
||||
for i in 0..tokens.len() {
|
||||
if tokens[i] == op {
|
||||
let l = &tokens[i - 1];
|
||||
let r = &tokens[i + 1];
|
||||
|
||||
let v = match op {
|
||||
Token::OpAdd => l.val() + r.val(),
|
||||
Token::OpDiv => l.val() / r.val(),
|
||||
Token::OpSub => l.val() - r.val(),
|
||||
Token::OpMult => l.val() * r.val(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
tokens.remove(i - 1);
|
||||
tokens.remove(i - 1);
|
||||
tokens[i - 1] = Token::Number(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Some(tokens[0].val());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
use anyhow::Result;
|
||||
use std::{
|
||||
io::{stdin, stdout, StdoutLock, Write},
|
||||
num::NonZeroU8,
|
||||
};
|
||||
use termion::{
|
||||
color::{self},
|
||||
cursor::HideCursor,
|
||||
event::Key,
|
||||
input::TermRead,
|
||||
raw::IntoRawMode,
|
||||
};
|
||||
|
||||
mod board;
|
||||
mod random;
|
||||
mod util;
|
||||
use board::Board;
|
||||
use util::{Player, Symb};
|
||||
|
||||
use crate::random::RandomAgent;
|
||||
|
||||
pub trait PlayerAgent {
|
||||
fn step(&mut self, board: &mut Board);
|
||||
}
|
||||
|
||||
fn play(
|
||||
stdout: &mut StdoutLock,
|
||||
player_max: bool,
|
||||
computer: &mut dyn PlayerAgent,
|
||||
) -> Result<Board> {
|
||||
let mut cursor = 0usize;
|
||||
let cursor_offset = 10usize - 1;
|
||||
let cursor_max = 10usize;
|
||||
|
||||
let mut board = Board::new(if player_max {
|
||||
Player::Human
|
||||
} else {
|
||||
Player::Computer
|
||||
});
|
||||
|
||||
let mut is_first = true;
|
||||
let mut print_board = true;
|
||||
'outer: loop {
|
||||
// Computer turn
|
||||
if board.current_player() == Player::Computer && !board.is_done() {
|
||||
computer.step(&mut board);
|
||||
}
|
||||
|
||||
let min_color = if !player_max {
|
||||
Player::Human.color()
|
||||
} else {
|
||||
Player::Computer.color()
|
||||
};
|
||||
|
||||
let max_color = if player_max {
|
||||
Player::Human.color()
|
||||
} else {
|
||||
Player::Computer.color()
|
||||
};
|
||||
|
||||
if print_board {
|
||||
println!(
|
||||
"\r{}\r{}Min{}/{}Max{} {}{}{}",
|
||||
" ".repeat(cursor_max + cursor_offset),
|
||||
color::Fg(min_color),
|
||||
color::Fg(color::Reset),
|
||||
color::Fg(max_color),
|
||||
color::Fg(color::Reset),
|
||||
if is_first { '╓' } else { '║' },
|
||||
board,
|
||||
if is_first { '╖' } else { '║' },
|
||||
);
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
print!(
|
||||
"\r{}╙{}{}{}{}{}╜",
|
||||
" ".repeat(cursor_offset),
|
||||
" ".repeat(cursor),
|
||||
color::Fg(board.current_player().color()),
|
||||
if board.is_done() { ' ' } else { '^' },
|
||||
color::Fg(color::Reset),
|
||||
" ".repeat(cursor_max - cursor),
|
||||
);
|
||||
stdout.flush()?;
|
||||
|
||||
if board.is_done() {
|
||||
break;
|
||||
}
|
||||
|
||||
let stdin = stdin();
|
||||
for c in stdin.keys() {
|
||||
print_board = match c.unwrap() {
|
||||
Key::Right => {
|
||||
cursor = cursor_max.min(cursor + 1);
|
||||
false
|
||||
}
|
||||
Key::Left => {
|
||||
if cursor != 0 {
|
||||
cursor -= 1
|
||||
}
|
||||
false
|
||||
}
|
||||
Key::Char('q') => break 'outer,
|
||||
Key::Char('1') => board.play(cursor, Symb::Number(NonZeroU8::new(1).unwrap())),
|
||||
Key::Char('2') => board.play(cursor, Symb::Number(NonZeroU8::new(2).unwrap())),
|
||||
Key::Char('3') => board.play(cursor, Symb::Number(NonZeroU8::new(3).unwrap())),
|
||||
Key::Char('4') => board.play(cursor, Symb::Number(NonZeroU8::new(4).unwrap())),
|
||||
Key::Char('5') => board.play(cursor, Symb::Number(NonZeroU8::new(5).unwrap())),
|
||||
Key::Char('6') => board.play(cursor, Symb::Number(NonZeroU8::new(6).unwrap())),
|
||||
Key::Char('7') => board.play(cursor, Symb::Number(NonZeroU8::new(7).unwrap())),
|
||||
Key::Char('8') => board.play(cursor, Symb::Number(NonZeroU8::new(8).unwrap())),
|
||||
Key::Char('9') => board.play(cursor, Symb::Number(NonZeroU8::new(9).unwrap())),
|
||||
Key::Char('0') => board.play(cursor, Symb::Zero),
|
||||
Key::Char('a') => board.play(cursor, Symb::Plus),
|
||||
Key::Char('s') => board.play(cursor, Symb::Minus),
|
||||
Key::Char('m') => board.play(cursor, Symb::Times),
|
||||
Key::Char('d') => board.play(cursor, Symb::Div),
|
||||
_ => false,
|
||||
};
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(board);
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let stdout = HideCursor::from(stdout().into_raw_mode().unwrap());
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let mut agent = RandomAgent {};
|
||||
let a = play(&mut stdout, true, &mut agent)?;
|
||||
if a.is_done() {
|
||||
println!(
|
||||
"\r\n{}Your score:{} {:.2}\n\n",
|
||||
color::Fg(Player::Human.color()),
|
||||
color::Fg(color::Reset),
|
||||
a.evaluate().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut agent = RandomAgent {};
|
||||
let b = play(&mut stdout, false, &mut agent)?;
|
||||
if b.is_done() {
|
||||
println!(
|
||||
"\r\n{}Computer score:{} {:.2}\n\n",
|
||||
color::Fg(Player::Computer.color()),
|
||||
color::Fg(color::Reset),
|
||||
b.evaluate().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
use std::num::NonZeroU8;
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{util::Symb, PlayerAgent};
|
||||
|
||||
pub struct RandomAgent {}
|
||||
|
||||
impl PlayerAgent for RandomAgent {
|
||||
fn step(&mut self, board: &mut crate::board::Board) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let n = board.size();
|
||||
|
||||
let mut c = rng.gen_range(0..n);
|
||||
let mut s = match rng.gen_range(0..4) {
|
||||
0 => {
|
||||
let n = rng.gen_range(0..=9);
|
||||
if n == 0 {
|
||||
Symb::Zero
|
||||
} else {
|
||||
Symb::Number(NonZeroU8::new(n).unwrap())
|
||||
}
|
||||
}
|
||||
1 => Symb::Div,
|
||||
2 => Symb::Minus,
|
||||
3 => Symb::Plus,
|
||||
4 => Symb::Times,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
while !board.play(c, s) {
|
||||
c = rng.gen_range(0..n);
|
||||
s = match rng.gen_range(0..4) {
|
||||
0 => {
|
||||
let n = rng.gen_range(0..=9);
|
||||
if n == 0 {
|
||||
Symb::Zero
|
||||
} else {
|
||||
Symb::Number(NonZeroU8::new(n).unwrap())
|
||||
}
|
||||
}
|
||||
1 => Symb::Div,
|
||||
2 => Symb::Minus,
|
||||
3 => Symb::Plus,
|
||||
4 => Symb::Times,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
use std::{fmt::Display, num::NonZeroU8};
|
||||
|
||||
use termion::color::{self, Color};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Player {
|
||||
Human,
|
||||
Computer,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn invert(&mut self) {
|
||||
match self {
|
||||
Self::Human => *self = Self::Computer,
|
||||
Self::Computer => *self = Self::Human,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(&self) -> &dyn Color {
|
||||
match self {
|
||||
Player::Computer => &color::LightBlack,
|
||||
Player::Human => &color::Magenta,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Player {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::Computer => "Max",
|
||||
Self::Human => "Min",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Symb {
|
||||
Number(NonZeroU8),
|
||||
Zero,
|
||||
Plus,
|
||||
Minus,
|
||||
Times,
|
||||
Div,
|
||||
}
|
||||
|
||||
impl Display for Symb {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Number(x) => x.fmt(f),
|
||||
Self::Zero => '0'.fmt(f),
|
||||
Self::Plus => '+'.fmt(f),
|
||||
Self::Minus => '-'.fmt(f),
|
||||
Self::Div => '÷'.fmt(f),
|
||||
Self::Times => '×'.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Symb {
|
||||
/// Is this symbol a plain binary operator?
|
||||
pub fn is_binop(&self) -> bool {
|
||||
match self {
|
||||
Symb::Div | Symb::Plus | Symb::Times => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_minus(&self) -> bool {
|
||||
self == &Self::Minus
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue