uno YAAAY

This commit is contained in:
mzntori 2024-03-23 19:50:45 +01:00
parent 803289d754
commit 0778aaab3d
6 changed files with 562 additions and 20 deletions

View file

@ -6,5 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.9.0-alpha.1"
twitch-irc = "5.0.1"
tokio = { version = "1.33.0", features = ["full"] }

View file

@ -32,6 +32,21 @@ pub struct Bot<'a, P> {
impl<'a, P> Bot<'a, P>
where P: Send + 'static
{
/// Creates a `Bot` instance using the given username and oauth-token.
/// `payload` can be any type and will be wrapped to `Arc<Mutex<P>>` and passed to any command executed,
/// where the user can access the contents mutable.
pub fn new<'b>(username: &'b str, oauth_token: &'b str, payload: P) -> Bot<'a, P>
where
'b : 'a
{
Bot {
username,
oauth_token,
payload: Arc::new(StdMutex::new(payload)),
commands: Arc::new(Mutex::new(HashMap::new())),
}
}
/// Return a message stream and a client wrapped in `Arc<Mutex<...>>`
fn incoming_messages_and_client(&self) -> (IncomingMessages, ClientAM) {
let login = self.username.to_owned();
@ -48,21 +63,6 @@ impl<'a, P> Bot<'a, P>
return (incoming_messages, client_am);
}
/// Creates a `Bot` instance using the given username and oauth-token.
/// `payload` can be any type and will be wrapped to `Arc<Mutex<P>>` and passed to any command executed,
/// where the user can access the contents mutable.
pub fn new<'b>(username: &'b str, oauth_token: &'b str, payload: P) -> Bot<'a, P>
where
'b : 'a
{
Bot {
username,
oauth_token,
payload: Arc::new(StdMutex::new(payload)),
commands: Arc::new(Mutex::new(HashMap::new())),
}
}
pub async fn add_command(&mut self, identifier: String, command: Box<dyn Command<CommandPayLoad=P>>) {
let mut commands = self.commands.lock().await;
commands.insert(identifier, command);
@ -73,7 +73,7 @@ impl<'a, P> Bot<'a, P>
let (mut incoming_messages, client_am) = self.incoming_messages_and_client();
let commands = Arc::clone(&self.commands);
let payload = Arc::clone(&self.payload);
let initial_channel = self.username.to_owned();
let initial_channel = "daph".to_string();
let msg_processor = tokio::spawn(async move {
{
@ -95,7 +95,10 @@ impl<'a, P> Bot<'a, P>
ServerMessage::Pong(_) => {}
ServerMessage::Privmsg(msg) => {
let mut cmd = commands.lock().await;
if let Some(command) = cmd.get_mut(&msg.message_text) {
let args: Vec<String> = msg.message_text.split(' ').map(|s| s.to_string()).collect();
if let Some(command) = cmd.get_mut(args.get(0).unwrap()) {
let mut queue = command.execute(Arc::clone(&payload), msg);
queue.execute(Arc::clone(&client_am)).await;
}

View file

@ -8,6 +8,10 @@ pub trait Command: Send {
type CommandPayLoad;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue;
fn help(&self) -> String;
fn info(&self) -> String;
fn help(&self) -> String {
"help".to_string()
}
fn info(&self) -> String {
"info".to_string()
}
}

View file

@ -18,7 +18,8 @@ impl Command for PingCommand {
let mut queue = ClientQueue::new();
let pl = pl_am.lock().unwrap();
queue.say("mzntori".to_string(), format!("Pong! {} {}", pl.content, ctx.sender.name));
queue.say(ctx.channel_login.to_owned(), format!("Pong! {} {}", pl.content, ctx.sender.name));
queue.say("gaygebot".to_string(), format!("Ponged in {}", ctx.channel_login));
println!("Pong executed! {}", pl.content);
queue

277
tests/uno.rs Normal file
View file

@ -0,0 +1,277 @@
use rand::prelude::*;
use std::fmt::{Display, Formatter, write};
use std::mem;
use crate::uno::UnoState::NotStarted;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Color {
Wild,
Blue,
Green,
Red,
Yellow,
}
impl Display for Color {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Color::Wild => { write!(f, "Wild") }
Color::Blue => { write!(f, "Blue") }
Color::Green => { write!(f, "Green") }
Color::Red => { write!(f, "Red") }
Color::Yellow => { write!(f, "Yellow") }
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Type {
Number(usize),
DrawTwo,
DrawFour,
ChooseColor,
Reverse,
Skip,
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Type::Number(n) => { write!(f, "{}", n) }
Type::DrawTwo => { write!(f, "+2") }
Type::DrawFour => { write!(f, "+4") }
Type::ChooseColor => { write!(f, "Choose Color") }
Type::Reverse => { write!(f, "Reverse") }
Type::Skip => { write!(f, "Skip") }
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct UnoCard {
typ: Type,
col: Color,
}
impl UnoCard {
pub fn new(typ: Type, col: Color) -> UnoCard {
UnoCard { typ, col }
}
pub fn can_stack(&self, other: &UnoCard) -> bool {
return if self.typ == other.typ || self.col == other.col || self.col == Color::Wild || other.col == Color::Wild {
true
} else {
false
};
}
}
impl Default for UnoCard {
fn default() -> Self {
UnoCard::new(Type::Number(0), Color::Wild)
}
}
impl Display for UnoCard {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.col, self.typ)
}
}
#[derive(Debug)]
pub struct UnoPlayer {
pub name: String,
pub hand: Vec<UnoCard>,
}
impl UnoPlayer {
pub fn new(name: String) -> Self {
UnoPlayer { name, hand: vec![] }
}
pub fn take_hand(&mut self) -> Vec<UnoCard> {
mem::take(&mut self.hand)
}
pub fn give_hand(&mut self, hand: Vec<UnoCard>) {
self.hand = hand;
}
pub fn take_card(&mut self, idx: usize) -> UnoCard {
let mut hand = self.take_hand();
let result = hand.remove(idx);
self.give_hand(hand);
result
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum UnoState {
NotStarted,
Started,
Ended,
}
fn new_uno_stack() -> Vec<UnoCard> {
let mut deck: Vec<UnoCard> = vec![];
for col in [Color::Blue, Color::Red, Color::Green, Color::Yellow] {
deck.push(UnoCard::new(Type::Number(0), col));
for n in 1..=9 {
deck.push(UnoCard::new(Type::Number(n), col));
deck.push(UnoCard::new(Type::Number(n), col));
}
for typ in [Type::Reverse, Type::Skip, Type::DrawTwo] {
deck.push(UnoCard::new(typ, col));
}
for _ in 0..4 {
deck.push(UnoCard::new(Type::DrawFour, Color::Wild));
deck.push(UnoCard::new(Type::ChooseColor, Color::Wild));
}
}
deck
}
#[derive(Debug)]
pub struct Uno {
card_stack: Vec<UnoCard>,
pub players: Vec<UnoPlayer>,
pub admin: String,
pub active_player: usize,
pub direction: i32,
pub draw_amount: u32,
pub state: UnoState,
pub top: UnoCard,
}
impl Uno {
pub fn new(admin: String) -> Self {
Uno {
card_stack: new_uno_stack(),
players: vec![],
admin,
active_player: 0,
direction: 1,
draw_amount: 0,
state: UnoState::Ended,
top: UnoCard::default(),
}
}
pub fn draw(&mut self) -> UnoCard {
self.card_stack.pop().unwrap()
}
pub fn end(&mut self) {
self.state = UnoState::Ended;
}
pub fn init(&mut self) {
self.state = UnoState::Started;
self.shuffle();
let mut card_buf: Vec<UnoCard> = vec![];
for _ in 0..(7 * self.players.len()) {
card_buf.push(self.draw());
}
for player in self.players.iter_mut() {
for _ in 0..7 {
player.hand.push(card_buf.pop().unwrap());
}
}
self.top = self.draw();
}
pub fn join(&mut self, name: String) {
if self.state == UnoState::NotStarted {
self.players.push(UnoPlayer::new(name));
}
}
pub fn kick(&mut self, name: String) {
let mut kick_idx: usize = usize::MAX;
for (i, p) in self.players.iter_mut().enumerate() {
if p.name == name {
let mut hand = p.take_hand();
self.card_stack.append(&mut hand);
kick_idx = i;
if i < self.active_player {
self.active_player -= 1;
}
}
}
if kick_idx < self.players.len() {
self.players.remove(kick_idx);
}
self.shuffle();
}
pub fn next_player(&mut self) {
let mult: i32 = match self.top.typ {
Type::Skip => { 2 }
_ => { 1 }
};
self.active_player = (self.active_player + self.players.len() + (self.direction * mult) as usize) % self.players.len();
}
pub fn place(&mut self, card: UnoCard) {
self.card_stack.push(mem::take(&mut self.top));
self.top = card;
self.shuffle();
match self.top.typ {
Type::Number(_) => { self.draw_amount = 0; }
Type::DrawTwo => { self.draw_amount += 2; }
Type::DrawFour => { self.draw_amount += 4; }
Type::ChooseColor => { self.draw_amount = 0; }
Type::Reverse => {
self.direction *= -1;
self.draw_amount = 0;
}
Type::Skip => { self.draw_amount = 0; }
}
self.next_player();
}
pub fn shuffle(&mut self) {
let mut rng = thread_rng();
self.card_stack.shuffle(&mut rng);
}
pub fn reset(&mut self, admin: String) {
self.admin = admin;
self.players = vec![];
self.card_stack = new_uno_stack();
self.active_player = 0;
self.direction = 1;
self.draw_amount = 0;
self.state = NotStarted;
self.top = UnoCard::default();
}
}

256
tests/uno_test.rs Normal file
View file

@ -0,0 +1,256 @@
pub mod uno;
use std::sync::{Arc, Mutex as StdMutex};
use twitch_irc::message::PrivmsgMessage;
use twitchbot_rs::{Bot, ClientQueue, Command, Context};
use crate::uno::{Uno, UnoPlayer, UnoState};
use crate::uno::UnoState::Ended;
pub struct PingCommand;
impl Command for PingCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: Context) -> ClientQueue {
let mut queue = ClientQueue::new();
let pl = pl_am.lock().unwrap();
queue.say(ctx.channel_login.to_owned(), format!("@{} Pong!", ctx.sender.name));
queue
}
}
pub struct UnoCommand;
impl Command for UnoCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let mut pl = pl_am.lock().unwrap();
if pl.state == Ended {
pl.reset(ctx.sender.name.clone());
queue.me(ctx.channel_login, format!("{} started a new uno game, type !join to join.", ctx.sender.name));
} else {
queue.me(ctx.channel_login, "Another game going on rn".to_string());
}
queue
}
}
pub struct DrawCommand;
impl Command for DrawCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let args: Vec<String> = ctx.message_text.split(' ').map(|s| s.to_string()).collect();
let mut pl = pl_am.lock().unwrap();
let idx = pl.active_player;
if pl.players.get_mut(idx).unwrap().name == ctx.sender.name && args.len() > 1 {
let amount = args
.get(1)
.unwrap()
.parse::<u32>()
.unwrap_or(0);
for _ in 0..amount {
let card = pl.draw();
pl.players.get_mut(idx).unwrap().hand.push(card);
}
queue.me(ctx.channel_login, format!("{} drew {} cards.", ctx.sender.name, amount));
} else {
queue.me(ctx.channel_login, "Sender probably not active player.".to_string())
}
queue
}
}
pub struct PlaceCommand;
impl Command for PlaceCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let args: Vec<String> = ctx.message_text.split(' ').map(|s| s.to_string()).collect();
let mut pl = pl_am.lock().unwrap();
let idx = pl.active_player;
if pl.players.get_mut(idx).unwrap().name == ctx.sender.name && args.len() > 1 {
let hand_idx = args
.get(1)
.unwrap()
.parse::<usize>()
.unwrap_or(0);
if hand_idx < pl.players.get_mut(idx).unwrap().hand.len() {
let card = pl.players.get_mut(idx).unwrap().take_card(hand_idx);
if card.can_stack(&pl.top) {
queue.me(ctx.channel_login, format!("{} placed {}", pl.players.get_mut(idx).unwrap().name, &card));
pl.place(card);
} else {
queue.me(ctx.channel_login, "Cant stack on that card.".to_string())
}
} else {
queue.me(ctx.channel_login, "Hand index out of range".to_string());
}
} else {
queue.me(ctx.channel_login, "Sender probably not active player.".to_string())
}
queue
}
}
pub struct JoinCommand;
impl Command for JoinCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let mut pl = pl_am.lock().unwrap();
if pl.state == UnoState::NotStarted {
pl.players.push(UnoPlayer::new(ctx.sender.name.clone()));
queue.me(ctx.channel_login, format!("{} Joined the game.", ctx.sender.name));
}
queue
}
}
pub struct StartCommand;
impl Command for StartCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let mut pl = pl_am.lock().unwrap();
if pl.admin == ctx.sender.name {
pl.init();
queue.me(ctx.channel_login, "Game started!".to_string());
}
queue
}
}
pub struct EndCommand;
impl Command for EndCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let mut pl = pl_am.lock().unwrap();
if pl.admin == ctx.sender.name {
pl.end();
queue.me(ctx.channel_login, "Game ended!".to_string());
}
queue
}
}
pub struct HandCommand;
impl Command for HandCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let args: Vec<String> = ctx.message_text.split(' ').map(|s| s.to_string()).collect();
let mut pl = pl_am.lock().unwrap();
let mut index = usize::MAX;
for (i, player) in pl.players.iter().enumerate() {
if player.name == ctx.sender.name {
index = i;
}
}
let mut card_list = String::new();
for card in pl.players.get(index).unwrap().hand.iter().enumerate() {
card_list.push_str(format!("{}: {}, ", card.0, card.1).as_str());
}
let channel: String = if ctx.sender.name == "daph" {
"daphbot".to_string()
} else {
ctx.sender.name
};
queue.say(channel, card_list);
queue
}
}
pub struct TopCommand;
impl Command for TopCommand {
type CommandPayLoad = Uno;
fn execute(&self, pl_am: Arc<StdMutex<Self::CommandPayLoad>>, ctx: PrivmsgMessage) -> ClientQueue {
let mut queue = ClientQueue::new();
let mut pl = pl_am.lock().unwrap();
queue.me(ctx.channel_login, format!("Top: {}", &pl.top));
queue
}
}
#[tokio::test]
async fn main() {
let mut u = Uno::new("mzntori".to_string());
let login = std::env::var("LOGIN").unwrap();
let oauth = std::env::var("OAUTH").unwrap();
let mut bot = Bot::new(
login.as_str(),
oauth.as_str(),
u,
);
bot.add_command("!ping".to_owned(), Box::new(PingCommand {})).await;
bot.add_command("!uno".to_owned(), Box::new(UnoCommand {})).await;
bot.add_command("!join".to_owned(), Box::new(JoinCommand {})).await;
bot.add_command("!draw".to_owned(), Box::new(DrawCommand {})).await;
bot.add_command("!start".to_owned(), Box::new(StartCommand {})).await;
bot.add_command("!end".to_owned(), Box::new(EndCommand {})).await;
bot.add_command("!place".to_owned(), Box::new(PlaceCommand {})).await;
bot.add_command("!hand".to_owned(), Box::new(HandCommand {})).await;
bot.add_command("!top".to_owned(), Box::new(TopCommand {})).await;
bot.run().await;
}