Initial commit: base95/220 and some other basics

This commit is contained in:
2026-01-14 18:33:21 +02:00
commit 1727cb9c55
10 changed files with 1158 additions and 0 deletions

303
src/base220.rs Normal file
View File

@@ -0,0 +1,303 @@
use core::fmt;
use std::fmt::Display;
// char offset for the zero value
pub const B220_ZERO: u8 = '#' as u8; // 0x23
///////////////////////////////////////////////////////////////////////////////////////////////////
// ERRORS /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub enum Base220Error {
IntOverflow(u32, usize),
}
impl fmt::Display for Base220Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Base220Error::IntOverflow(n, len) => write!(f, "input number {n} is too long for {len} bytes"),
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// STRUCTS ////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Base220Digit(u8);
impl TryFrom<u8> for Base220Digit {
type Error = String;
fn try_from(value: u8) -> Result<Self, Self::Error> {
const MAX_VALUE: u8 = 255 - B220_ZERO;
if value <= MAX_VALUE {
Ok(Self(value + B220_ZERO))
} else {
Err(format!("base220 value {value} is too high (bigger than {MAX_VALUE})"))
}
}
}
impl From<Base220Digit> for u8 {
fn from(value: Base220Digit) -> Self {
// precaution
if value.0 < B220_ZERO {
panic!("Base220Digit({}) stored value is too low (min: {})!", value.0, B220_ZERO);
}
value.0 - B220_ZERO
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Base220(pub u32);
impl Base220 {
/// Returns the integer value of this base220 number
///
/// # Examples
///
/// ```
/// use furc::base220::Base220;
/// let raw_b220_value = b"$$"; // 221
/// let b220 = Base220::from(raw_b220_value);
/// assert_eq!(b220.value(), 221);
/// ```
pub fn value(&self) -> u32 {
self.0
}
pub fn as_owned_str(&self) -> String {
to_base220vec(self.0).iter().map(|&c| c as char).collect()
}
pub fn to_vec(&self) -> Vec<u8> {
to_base220vec(self.0)
}
pub fn to_vec_size(&self, size: usize) -> Result<Vec<u8>, Base220Error> {
let mut b220: Vec<u8> = Vec::with_capacity(size);
match to_base220(self.0, &mut b220) {
Ok(_) => Ok(b220),
Err(e) => Err(e),
}
}
pub fn store<'a>(&self, bytes: &'a mut [u8]) -> Result<&'a [u8], Base220Error> {
to_base220(self.0, bytes)
}
}
impl Display for Base220 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_owned_str())
}
}
impl From<Base220> for u32 {
fn from(value: Base220) -> Self {
value.0
}
}
impl From<u32> for Base220 {
fn from(value: u32) -> Self {
Self(value)
}
}
impl<const N: usize> From<&[u8; N]> for Base220 {
fn from(value: &[u8; N]) -> Self {
Self(from_base220(value))
}
}
impl From<&[u8]> for Base220 {
fn from(value: &[u8]) -> Self {
Self(from_base220(value))
}
}
impl From<&str> for Base220 {
fn from(value: &str) -> Self {
Self::from(value.as_bytes())
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
pub fn from_base220(b220: &[u8]) -> u32 {
let mut acc = 0;
let mut multiplier = 1;
for ch in b220.iter() {
acc += multiplier * ((*ch - B220_ZERO) as u32);
multiplier *= 220;
}
return acc;
}
pub fn to_base220(n: u32, dest: &mut [u8]) -> Result<&[u8], Base220Error> {
let mut acc = n;
// least significant digit first
for i in 0..dest.len() {
dest[i] = ((acc % 220) as u8) + B220_ZERO;
acc /= 220;
}
// if we processed all of the digits, acc will be 0
match acc {
0 => Ok(dest),
_ => Err(Base220Error::IntOverflow(n, dest.len())),
}
}
pub fn to_base220vec(n: u32) -> Vec<u8> {
if n == 0 {
return [B220_ZERO].to_vec();
}
let mut acc = n;
let mut b220: Vec<u8> = Vec::new();
while acc > 0 {
b220.push((acc % 220) as u8 + B220_ZERO);
acc /= 220;
}
return b220;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// TESTS //////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use super::*;
///////////////////////////////////////////////////////////////////////////
// Base220Digit ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
#[test]
fn base220digit() {
assert!(Base220Digit::try_from(0).is_err());
assert!(Base220Digit::try_from(1).is_err());
assert!(Base220Digit::try_from(220).is_err());
assert!(!Base220Digit::try_from(221).is_err());
// check value conversion
for value in vec![0, 1, 220] {
match Base220Digit::try_from(value) {
Ok(b220digit) => {
let v: u8 = b220digit.into();
assert_eq!(v, value);
},
Err(e) => panic!("Base220Digit::try_from({}) failed: {}", value, e),
}
}
// check invalild values
for value in 221..256 {
match Base220Digit::try_from(value) {
Ok(b220digit) => panic!("Base220Digit::try_from({}) succeeded: {:?}", value, b220digit),
Err(e) => assert!(e.contains("too high")),
}
}
}
///////////////////////////////////////////////////////////////////////////
// FUNCTIONS //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/// Returns all *(bytes, u32)* example tuples for base220 conversion
fn get_base220_conversion_table() -> Vec<(&'static [u8], u32)> {
vec![
// special case
(b"", 0),
// 1 digit
(b"#", 0),
(b"$", 1),
(b"\xFE", 219),
// 2 digits
(b"##", 0),
(b"$#", 1),
(b"#$", 220),
(b"$$", 221),
(b"4)", 1337),
(b"\xFE\xFE", 48399),
// 3 digits
(b"$##", 1),
(b"##$", 48400),
]
}
#[test]
fn test_from_base220() {
for (b220, num) in get_base220_conversion_table() {
assert_eq!(num, from_base220(b220))
}
}
#[test]
fn test_to_base220() {
// test correct approach with properly sized buffer
for (b220, num) in get_base220_conversion_table() {
let mut buf: Vec<u8> = vec![0u8; b220.len()];
assert_eq!(to_base220(num, &mut buf).unwrap(), b220);
}
// test correct approach with over-sized buffer
for (b220, num) in get_base220_conversion_table() {
let mut buf: Vec<u8> = vec![0u8; b220.len() + 1];
let b220result = to_base220(num, &mut buf).unwrap();
// most significant byte should change from 0 to B220_ZERO
assert_eq!(b220result[b220result.len() - 1], B220_ZERO);
assert_eq!(
&b220result[..b220result.len()-1],
b220,
"to_base220({num}, buf) -?-> {b220:?}");
}
// test undersized buffer & Base220Error::IntOverflow
let mut buf = [0u8; 1];
assert!(to_base220(219, &mut buf).is_ok());
let result = to_base220(220, &mut buf);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Base220Error::IntOverflow(220, 1)));
}
#[test]
fn test_to_base220vec() {
for (b220, num) in get_base220_conversion_table() {
// skip zero padding, but expect at least one byte in the result!
let b220expected = match num {
0 => &[B220_ZERO],
_ => match b220.iter().rev().position(|&c| c != B220_ZERO) {
Some(end_off) => &b220[..b220.len()-end_off],
None => b220,
}
};
assert_ne!(b220expected.len(), 0, "invalid expected b220 {:?} for input {}", b220, num);
assert_eq!(to_base220vec(num), b220expected, "input: {num}, expected: {b220expected:?}");
}
}
}

276
src/base95.rs Normal file
View File

@@ -0,0 +1,276 @@
use core::fmt;
use std::fmt::Display;
// char offset for the zero value
static B95_ZERO: u8 = ' ' as u8; // 0x20
///////////////////////////////////////////////////////////////////////////////////////////////////
// ERRORS /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub enum Base95Error {
IntOverflow(u32, usize),
}
impl fmt::Display for Base95Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Base95Error::IntOverflow(n, len) => write!(f, "input number {n} is too long for {len} bytes"),
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// STRUCTS ////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct Base95(pub u32);
impl Base95 {
pub fn as_owned_str(&self) -> String {
to_base95vec(self.0).iter().map(|&c| c as char).collect()
}
pub fn to_vec(&self) -> Vec<u8> {
to_base95vec(self.0)
}
pub fn to_vec_size(&self, size: usize) -> Result<Vec<u8>, Base95Error> {
let mut b95: Vec<u8> = Vec::with_capacity(size);
match to_base95(self.0, &mut b95) {
Ok(_) => Ok(b95),
Err(e) => Err(e),
}
}
pub fn store<'a>(&self, bytes: &'a mut [u8]) -> Result<&'a [u8], Base95Error> {
to_base95(self.0, bytes)
}
}
impl Display for Base95 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_owned_str())
}
}
impl From<Base95> for u32 {
fn from(value: Base95) -> Self {
value.0
}
}
impl From<u32> for Base95 {
fn from(value: u32) -> Self {
Self(value)
}
}
impl<const N: usize> From<&[u8; N]> for Base95 {
fn from(value: &[u8; N]) -> Self {
Self(from_base95(value))
}
}
impl From<&[u8]> for Base95 {
fn from(value: &[u8]) -> Self {
Self(from_base95(value))
}
}
impl From<&str> for Base95 {
fn from(value: &str) -> Self {
Self::from(value.as_bytes())
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Converts a base95 number represented by a given slice - into a 32
///
/// # Examples
///
/// ```
/// use furc::base95::from_base95;
/// assert_eq!(1, from_base95(b" !"));
/// assert_eq!(1, from_base95(b"!"));
/// assert_eq!(95, from_base95(b"! "));
/// assert_eq!(1337, from_base95(b".'"));
/// assert_eq!(9025, from_base95(b"! "));
/// ```
pub fn from_base95(b95: &[u8]) -> u32 {
let mut acc = 0;
let mut multiplier = 1;
for ch in b95.iter().rev() {
acc += multiplier * ((*ch - B95_ZERO) as u32);
multiplier *= 95;
}
return acc;
}
/// Converts a number into a base95 number, and stores it in `dest`
///
/// # Examples
/// ```
/// use furc::base95::to_base95;
/// use furc::base95::Base95Error;
///
/// let mut dest = [0; 2];
/// assert_eq!(b" ", to_base95(0, &mut dest).unwrap());
/// assert_eq!(b" !", to_base95(1, &mut dest).unwrap());
/// assert_eq!(b"! ", to_base95(95, &mut dest).unwrap());
/// assert_eq!(b"!!", to_base95(96, &mut dest).unwrap());
/// assert_eq!(b".'", to_base95(1337, &mut dest).unwrap());
/// assert_eq!(b"~~", to_base95(9024, &mut dest).unwrap());
///
/// let mut buf1 = [0; 1];
/// assert!(to_base95(95, &mut buf1).is_err());
/// assert!(matches!(to_base95(95, &mut buf1).unwrap_err(), Base95Error::IntOverflow(95, 1)));
///
/// match to_base95(95, &mut buf1) {
/// Ok(_) => panic!("crammed a 2-digit number inside 1 byte, wtf?"),
/// Err(Base95Error::IntOverflow(n, len)) => println!("{n} is too big for an array of {len} :("),
/// Err(e) => panic!("unexpected error: {e:?}"),
/// }
/// ```
pub fn to_base95(n: u32, dest: &mut [u8]) -> Result<&[u8], Base95Error> {
let mut acc = n;
// least significant digit last
for i in (0..dest.len()).rev() {
dest[i] = ((acc % 95) as u8) + B95_ZERO;
acc /= 95;
}
// if we processed all of the digits, acc will be 0
match acc {
0 => Ok(dest),
_ => Err(Base95Error::IntOverflow(n, dest.len())),
}
}
/// Converts a number into a base95 vector of an arbitrary size
///
/// # Examples
/// ```
/// use furc::base95::to_base95vec;
///
/// assert_eq!(to_base95vec(0), b" ");
/// assert_eq!(to_base95vec(1), b"!");
/// assert_eq!(to_base95vec(95), b"! ");
/// assert_eq!(to_base95vec(96), b"!!");
/// assert_eq!(to_base95vec(1337), b".'");
/// assert_eq!(to_base95vec(9024), b"~~");
/// ```
pub fn to_base95vec(n: u32) -> Vec<u8> {
if n == 0 {
return [B95_ZERO].to_vec();
}
let mut acc = n;
let mut b95: Vec<u8> = Vec::new();
while acc > 0 {
b95.push((acc % 95) as u8 + B95_ZERO);
acc /= 95;
}
b95.reverse();
return b95;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// TESTS //////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use super::*;
/// Returns all *(bytes, u32)* example tuples for base95 conversion
fn get_base95_conversion_table() -> Vec<(&'static [u8], u32)> {
vec![
// special case
(b"", 0),
// 1 digit
(b" ", 0),
(b" ", 0),
(b"!", 1),
(b"~", 94),
// 2 digits & order
(b" ", 0),
(b" !", 1),
(b"! ", 95),
(b"!!", 96),
(b".'", 1337),
(b"~~", 9024),
// 3 digits
(b" !", 1),
(b"! ", 9025),
]
}
#[test]
fn test_from_base95() {
for (b95, num) in get_base95_conversion_table() {
assert_eq!(from_base95(b95), num);
}
}
#[test]
fn test_to_base95() {
// test correct approach with properly sized buffer
for (b95, num) in get_base95_conversion_table() {
let mut buf: Vec<u8> = vec![0u8; b95.len()];
assert_eq!(to_base95(num, &mut buf).unwrap(), b95);
}
// test correct approach with over-sized buffer
for (b95, num) in get_base95_conversion_table() {
let mut buf: Vec<u8> = vec![0u8; b95.len() + 1];
let b95result = to_base95(num, &mut buf).unwrap();
// most significant byte should change from 0 to B95_ZERO
assert_eq!(b95result[0], B95_ZERO);
assert_eq!(
&b95result[1..],
b95,
"to_base95({num}, buf) -?-> {b95:?}");
}
// test undersized buffer & Base95Error::IntOverflow
let mut buf = [0u8; 1];
assert!(to_base95(94, &mut buf).is_ok());
let result = to_base95(95, &mut buf);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Base95Error::IntOverflow(95, 1)));
}
#[test]
fn test_to_base95vec() {
for (b95, num) in get_base95_conversion_table() {
// skip zero padding, but expect at least one byte in the result!
let b95expected = match num {
0 => b" ",
_ => match b95.iter().position(|&c| c != B95_ZERO) {
Some(start) => &b95[start..],
None => b95,
},
};
assert_ne!(b95expected.len(), 0, "invalid expected b95 {:?} for input {}", b95, num);
assert_eq!(to_base95vec(num), b95expected, "input: {num}, expected: {b95expected:?}");
}
}
}

73
src/bin/base-convert.rs Normal file
View File

@@ -0,0 +1,73 @@
use clap::Parser;
use furc::base95::{from_base95, to_base95vec};
use furc::base220::{from_base220, to_base220vec};
#[derive(Parser)]
#[command(name = "base-convert")]
#[command(version = "1.0")]
#[command(about = "Convert base95 and base220 Furcadia numbers", long_about = None)]
struct Cli {
#[clap(short, long, conflicts_with = "to", help = "Convert FROM base(...) to decimal (default)")]
from: bool,
#[clap(short, long, default_value_t = true, conflicts_with = "from", help = "Convert TO base(...) from decimal")]
to: bool,
#[clap(long, default_value_t = true, conflicts_with = "b220", help = "Convert to/from base95 (default)")]
b95: bool,
#[clap(long, conflicts_with = "b95", help = "Convert to/from base220")]
b220: bool,
#[clap(short, long, conflicts_with = "from")]
size: Option<u8>, // <- optional argumnet
#[clap(short, long, help = "Input number")]
input: String,
}
fn main() {
let mut cli = Cli::parse();
if cli.b220 { cli.b95 = false }
if cli.from { cli.to = false }
// ->base(...)
if cli.to {
// read input as decimal
let input = match cli.input.parse::<u32>() {
Ok(n) => n,
Err(e) => panic!("Error: invalid input {:?} ({:?})", cli.input, e),
};
// convert decimal input into base95 or base220 based on choice
let result: String = if cli.b95 {
to_base95vec(input)
} else if cli.b220 {
to_base220vec(input)
} else {
panic!("unknown conversion mode (neither --b95, nor --b220)");
}
.into_iter()
.map(|c| c as char)
.collect();
print!("{}", result);
} else if cli.from {
// read input as bytes
let input = cli.input.as_bytes();
// convert into decimal
let result: u32 = if cli.b95 {
from_base95(input)
} else if cli.b220 {
from_base220(input)
} else {
panic!("unknown conversion mode (neither --b95, nor --b220)")
};
print!("{result}");
} else {
panic!("unknown direction (neither --to, nor --from)");
}
}

166
src/colors.rs Normal file
View File

@@ -0,0 +1,166 @@
use std::u8;
use crate::base220::Base220Digit;
use super::base220::B220_ZERO;
enum Gender {
Male,
Female,
Unspecified,
Unknown(u8),
}
impl From<Gender> for u8 {
fn from(value: Gender) -> Self {
match value {
Gender::Female => 0,
Gender::Male => 1,
Gender::Unspecified => 2,
Gender::Unknown(value) => value,
}
}
}
impl From<u8> for Gender {
fn from(value: u8) -> Self {
match value {
0 => Gender::Female,
1 => Gender::Male,
2 => Gender::Unspecified,
v => Gender::Unknown(v),
}
}
}
enum BaseSpecies {
Rodent,
Equine,
Feline,
Canine,
Mustline,
Lapine,
Squirrel,
Bovine,
Unknown(u8),
}
impl From<BaseSpecies> for u8 {
fn from(value: BaseSpecies) -> Self {
match value {
BaseSpecies::Rodent => 0,
BaseSpecies::Equine => 1,
BaseSpecies::Feline => 2,
BaseSpecies::Canine => 3,
BaseSpecies::Mustline => 4,
BaseSpecies::Lapine => 5,
BaseSpecies::Squirrel => 6,
BaseSpecies::Bovine => 7,
BaseSpecies::Unknown(v) => v,
}
}
}
impl From<u8> for BaseSpecies {
fn from(value: u8) -> Self {
match value {
0 => BaseSpecies::Rodent,
1 => BaseSpecies::Equine,
2 => BaseSpecies::Feline,
3 => BaseSpecies::Canine,
4 => BaseSpecies::Mustline,
5 => BaseSpecies::Lapine,
6 => BaseSpecies::Squirrel,
7 => BaseSpecies::Bovine,
v => BaseSpecies::Unknown(v),
}
}
}
struct Colors([u8; 14]);
impl Colors {
pub const TYPE_BYTE: u8 = b't';
// Offsets ////////////////////////////////////////////////////////////////
pub const OFFSET_FUR: usize = 1;
pub const OFFSET_MARKINGS: usize = 2;
pub const OFFSET_HAIR: usize = 3;
pub const OFFSET_EYE: usize = 4;
pub const OFFSET_BADGE: usize = 5;
pub const OFFSET_VEST: usize = 6;
pub const OFFSET_BRACER: usize = 7;
pub const OFFSET_CAPE: usize = 8;
pub const OFFSET_BOOT: usize = 9;
pub const OFFSET_TROUSER: usize = 10;
pub const OFFSET_GENDER: usize = 11;
pub const OFFSET_SPECIES: usize = 12;
pub const OFFSET_SPECIAL: usize = 13;
// Static /////////////////////////////////////////////////////////////////
pub fn new() -> Colors {
let mut c = [B220_ZERO; 14];
c[0] = Self::TYPE_BYTE;
Self(c)
}
// Methods ////////////////////////////////////////////////////////////////
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn gender(&self) -> Gender {
Gender::from(self.0[Self::OFFSET_GENDER])
}
pub fn set_gender(&mut self, gender: Gender) {
self.0[Self::OFFSET_GENDER] = gender.into()
}
pub fn species(&self) -> BaseSpecies {
BaseSpecies::from(self.0[Self::OFFSET_SPECIES])
}
pub fn set_species(&mut self, species: BaseSpecies) {
self.0[Self::OFFSET_SPECIES] = species.into()
}
}
impl From<&[u8]> for Colors {
fn from(value: &[u8]) -> Self {
// ensure value is a compatible color type
if value[0] != Self::TYPE_BYTE {
panic!("Colors::from({:?}): input starts with {:?} and not with {:?}", value, value[0] as char, Self::TYPE_BYTE);
}
// ensure value isn't too short
if value.len() < 14 {
panic!("Colors::from({:?}): expects a slice of at least 14 bytes, got {}", value, value.len());
}
let mut arr = [B220_ZERO; 14];
arr.copy_from_slice(&value[..14]);
Colors(arr)
}
}
impl From<String> for Colors {
fn from(value: String) -> Self {
Self::from(value.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_colors_default() {
let col = Colors::new();
// ensure the raw value starts with 't' and is all base220 zeroes
let col_bytes = col.as_bytes();
assert_eq!(col_bytes[0], b't');
for &c in col_bytes.iter().skip(1) {
assert_eq!(c, B220_ZERO);
}
}
}

101
src/conn.rs Normal file
View File

@@ -0,0 +1,101 @@
static FURC_BUFSZ: i32 = 4096;
pub trait FurcOutput {
fn send_bytes(&self, msg: &[u8]);
fn send_str(&self, msg: String) {
self.send_bytes(msg.as_bytes());
}
}
pub struct Protocol
{
output: dyn FurcOutput,
}
pub enum MoveDirection {
SW = 1,
SE = 3,
NW = 7,
NE = 9,
}
#[repr(u8)]
pub enum TurnDirection {
CW = b'>', // clockwise
CCW = b'<', // counter-clockwise
}
impl Into<u8> for TurnDirection {
fn into(self) -> u8 {
self as u8
}
}
pub enum ClientMessage {
Raw(String),
Account { email: String, name: String, password: String },
Connect { name: String, password: String },
Turn(TurnDirection),
Move(MoveDirection),
Say(String),
Emote(String),
Shout(String),
ToggleShouts,
Whisper { name: String, msg: String },
WhisperExact { name: String, msg: String },
WhisperOffline { name: String, msg: String },
MakeCookie { name: String, msg: String },
GiveCookie { name: String, msg: String },
Who,
Join(String),
JoinLast,
SummonLast,
Summon(String),
Decline,
Vascodagama,
Quit,
Desc(String),
Colors([u8; 13]),
}
impl Protocol {
pub fn account(&self, email: &str, name: &str, password: &str) {
self.output.send_str(format!("account {email} {name} {password}"))
}
pub fn connect(&self, name: &str, password: &str) {
self.output.send_str(format!("connect {name} {password}"))
}
pub fn turn(&self, dir: TurnDirection) {
self.output.send_bytes(&[dir.into()]);
}
pub fn turn_cw(&self) {
self.output.send_str(">".to_owned());
}
pub fn turn_ccw(&self) {
self.output.send_str("<".to_owned());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_turn_direction() {
let cw = TurnDirection::CW;
let cw_byte: u8 = cw.into();
assert_eq!(cw_byte, b'>');
let ccw = TurnDirection::CCW;
let ccw_byte: u8 = ccw.into();
assert_eq!(ccw_byte, b'<');
}
}

8
src/lib.rs Normal file
View File

@@ -0,0 +1,8 @@
pub mod base95;
pub mod base220;
pub mod conn;
pub mod colors;
// fn main() {
// println!("Hello, world!");
// }