Skip to content
Snippets Groups Projects
Select Git revision
  • e7874fdfe44022211d17b027fd0a2ab4d9898e61
  • master default protected
  • angular-refactor
3 results

describe_Game_spec.js

Blame
  • xbasic.rs 9.53 KiB
    use crate::framebuffer::FrameBuffer;
    use crate::handlers::LineHandler;
    use crate::program::Program;
    
    use diesel::PgConnection;
    use serenity::async_trait;
    use serenity::model::channel::{AttachmentType, Message, ReactionType};
    use serenity::model::id::UserId;
    use serenity::prelude::*;
    use std::borrow::{Borrow, Cow};
    use std::collections::HashMap;
    use std::str::FromStr;
    use std::sync::Arc;
    use tokio::sync::Mutex;
    use tokio::task;
    use xbasic::basic_io::BasicIO;
    use xbasic::expr::ExprValue;
    use xbasic::xbasic::XBasicBuilder;
    
    struct DiscordIO {
    	s: String,
    	frame: Option<FrameBuffer>,
    }
    
    impl DiscordIO {
    	fn new() -> Self {
    		Self {
    			s: String::new(),
    			frame: None,
    		}
    	}
    }
    
    impl BasicIO for DiscordIO {
    	fn read_line(&mut self) -> String {
    		unimplemented!()
    	}
    
    	fn write_line(&mut self, line: String) {
    		self.s += &*(line + "\r\n");
    	}
    }
    
    macro_rules! get_user_programs {
    	($self: expr, $user_id: expr) => {
    		$self.programs.lock().await.get_mut($user_id).unwrap()
    	};
    }
    
    pub struct XbasicHandler {
    	programs: Arc<Mutex<HashMap<UserId, Program>>>,
    	conn: Arc<Mutex<PgConnection>>,
    }
    
    impl XbasicHandler {
    	pub fn new(conn: Arc<Mutex<PgConnection>>) -> Self {
    		Self {
    			programs: Arc::new(Mutex::new(HashMap::new())),
    			conn,
    		}
    	}
    
    	async fn interpret_line(&self, msg: &Message, ctx: &Context, line: &str) {
    		// TODO we lock the mutex to check, but unlock before locking again later
    		// allows another thread to screw it up
    		// we should lock once for this entire function
    		if self.programs.lock().await.contains_key(&msg.author.id) {
    			match line {
    				"!STOP" => {
    					self.interpreter_stop(msg).await;
    				}
    				"RUN" => {
    					self.interpreter_run(msg, ctx).await;
    				}
    				"LIST" => {
    					self.interpreter_list(msg, ctx).await;
    				}
    				"DIR" => {
    					if self.list_saved_programs(msg, ctx).await.is_none() {
    						msg.channel_id
    							.say(&ctx, "Could not get list of programs from database.")
    							.await
    							.unwrap();
    					}
    				}
    				"PUBDIR" => {
    					if self.list_published_programs(msg, ctx).await.is_none() {
    						msg.channel_id
    							.say(
    								&ctx,
    								"Could not get list of published programs from database.",
    							)
    							.await
    							.unwrap();
    					}
    				}
    				_ => {
    					if let Some(name) = line.strip_prefix("SAVE ") {
    						self.interpreter_save(name, msg, ctx).await;
    					}
    
    					if let Some(name) = line.strip_prefix("LOAD ") {
    						self.interpreter_load(name, msg, ctx).await;
    					}
    
    					if let Some(name) = line.strip_prefix("PUB ") {
    						if self.publish_program(name, msg, ctx).await.is_none() {
    							msg.channel_id
    								.say(&ctx, format!("Could not publish {name}."))
    								.await
    								.unwrap();
    						}
    					}
    
    					if let Some(name) = line.strip_prefix("UNPUB ") {
    						if self.unpublish_program(name, msg, ctx).await.is_none() {
    							msg.channel_id
    								.say(&ctx, &format!("Could not unpublish {name}."))
    								.await
    								.unwrap();
    						}
    					}
    
    					if let Some(id_str) = line.strip_prefix("PUBLOAD ") {
    						match id_str.parse::<i32>() {
    							Ok(id) => {
    								if self.load_published_program(msg, ctx, id).await.is_none() {
    									msg.channel_id
    										.say(&ctx, format!("Could not load {id}."))
    										.await
    										.unwrap();
    								}
    							}
    							Err(_) => {
    								msg.channel_id
    									.say(&ctx, "Error: PUBLOAD requires a numerical ID.")
    									.await
    									.unwrap();
    							}
    						}
    					}
    
    					let mut split = line.splitn(2, ' ');
    					let first = split.next().unwrap();
    					if let Ok(num) = first.parse::<u32>() {
    						match split.next() {
    							Some(x) => {
    								if x.is_empty() {
    									let _ =
    										get_user_programs!(self, &msg.author.id).code.remove(&num);
    									return;
    								}
    
    								get_user_programs!(self, &msg.author.id)
    									.code
    									.insert(num, x.to_owned());
    							}
    							None => {
    								let _ = get_user_programs!(self, &msg.author.id).code.remove(&num);
    							}
    						}
    					}
    				}
    			}
    		}
    
    		if line == "!START" {
    			self.interpreter_start(msg).await;
    		}
    	}
    
    	async fn interpreter_stop(&self, msg: &Message) {
    		self.programs.lock().await.remove(&msg.author.id).unwrap();
    	}
    
    	async fn interpreter_start(&self, msg: &Message) {
    		self.programs
    			.lock()
    			.await
    			.insert(msg.author.id, Program::new());
    	}
    
    	async fn list_saved_programs(&self, msg: &Message, ctx: &Context) -> Option<()> {
    		let program_names =
    			Program::list_programs_by_user(self.conn.lock().await.borrow(), msg.author.id)?;
    		msg.channel_id
    			.say(
    				&ctx,
    				format!(
    					"You have {} programs saved:\r\n```\r\n{}\r\n```",
    					program_names.len(),
    					program_names.join("\r\n")
    				),
    			)
    			.await
    			.ok()?;
    
    		Some(())
    	}
    
    	async fn list_published_programs(&self, msg: &Message, ctx: &Context) -> Option<()> {
    		let program_names: Vec<String> =
    			Program::list_published_programs(self.conn.lock().await.borrow())?
    				.iter()
    				.map(|row| format!("{}\t{}", row.0, row.1))
    				.collect();
    
    		msg.channel_id
    			.say(
    				&ctx,
    				format!(
    				"Found {} public programs:\r\n```\r\nid\tname\r\n{}\r\n```\r\n Load one with `PUBLOAD <id>`",
    					program_names.len(),
    				program_names.join("\r\n")),
    			)
    			.await
    			.unwrap();
    
    		Some(())
    	}
    
    	async fn publish_program(&self, name: &str, msg: &Message, ctx: &Context) -> Option<()> {
    		Program::set_program_published(self.conn.lock().await.borrow(), name, msg.author.id, true)?;
    
    		msg.channel_id
    			.say(&ctx, format!("Published {name}."))
    			.await
    			.unwrap();
    
    		Some(())
    	}
    
    	async fn unpublish_program(&self, name: &str, msg: &Message, ctx: &Context) -> Option<()> {
    		Program::set_program_published(
    			self.conn.lock().await.borrow(),
    			name,
    			msg.author.id,
    			false,
    		)?;
    
    		msg.channel_id
    			.say(&ctx, format!("Unpublished {name}."))
    			.await
    			.unwrap();
    
    		Some(())
    	}
    
    	async fn load_published_program(&self, msg: &Message, ctx: &Context, id: i32) -> Option<()> {
    		let name = get_user_programs!(self, &msg.author.id)
    			.load_published_program(self.conn.lock().await.borrow(), id)?;
    
    		msg.channel_id
    			.say(&ctx, format!("Loaded {id} (\"{name}\") into memory."))
    			.await
    			.unwrap();
    
    		Some(())
    	}
    
    	async fn interpreter_list(&self, msg: &Message, ctx: &Context) {
    		msg.channel_id
    			.say(
    				&ctx,
    				format!(
    					"```\n{}\n```",
    					self.programs.lock().await[&msg.author.id].stringy_line_nums()
    				),
    			)
    			.await
    			.unwrap();
    	}
    
    	async fn interpreter_load(&self, name: &str, msg: &Message, ctx: &Context) {
    		let result = get_user_programs!(self, &msg.author.id).load_program(
    			&*self.conn.lock().await,
    			msg.author.id,
    			name,
    		);
    		match result {
    			Some(_) => {
    				msg.channel_id
    					.say(&ctx, format!("Loaded {name} into memory."))
    					.await
    					.unwrap();
    			}
    			None => {
    				msg.channel_id
    					.say(&ctx, "Could not load program into memory.")
    					.await
    					.unwrap();
    			}
    		}
    	}
    
    	async fn interpreter_save(&self, name: &str, msg: &Message, ctx: &Context) {
    		let result = get_user_programs!(self, &msg.author.id).save_program(
    			&*self.conn.lock().await,
    			msg.author.id,
    			name,
    		);
    		match result {
    			Some(_) => {
    				msg.channel_id
    					.say(&ctx, format!("Saved as {name}"))
    					.await
    					.unwrap();
    			}
    			None => {
    				msg.channel_id
    					.say(&ctx, "Could not save program.")
    					.await
    					.unwrap();
    			}
    		}
    	}
    
    	async fn interpreter_run(&self, msg: &Message, ctx: &Context) {
    		let thinking_react = match ReactionType::from_str("🤔") {
    			Ok(react) => msg.react(&ctx, react).await.ok(),
    			Err(_) => None,
    		};
    
    		let code = self.programs.lock().await[&msg.author.id].stringify();
    		let io = DiscordIO::new();
    
    		let (output, fb, errors) = task::spawn_blocking(move || {
    			let mut xbb = XBasicBuilder::new(io);
    			xbb.compute_limit(1000000000);
    
    			xbb.define_function("setframe".to_owned(), 2, |args, io| {
    				let w = args[0].clone().into_decimal() as u32;
    				let h = args[1].clone().into_decimal() as u32;
    
    				io.frame = Some(FrameBuffer::new(w, h));
    
    				ExprValue::Decimal(0.0)
    			})
    			.unwrap();
    
    			xbb.define_function("setpixel".to_owned(), 5, |args, io| {
    				let x = args[0].clone().into_decimal() as u32;
    				let y = args[1].clone().into_decimal() as u32;
    				let red = args[2].clone().into_decimal() as u8;
    				let green = args[3].clone().into_decimal() as u8;
    				let blue = args[4].clone().into_decimal() as u8;
    
    				match &mut io.frame {
    					Some(fb) => {
    						fb.set_pixel(x, y, red, green, blue, 255);
    					}
    					None => {}
    				}
    
    				ExprValue::Decimal(0.0)
    			})
    			.unwrap();
    
    			let mut xb = xbb.build();
    
    			let _ = xb.run(&format!("{code}\n"));
    
    			let errors = if xb.error_handler.had_errors || xb.error_handler.had_runtime_error {
    				Some(xb.error_handler.errors.join("\n"))
    			} else {
    				None
    			};
    
    			(xb.get_io().s.clone(), xb.get_io().frame.clone(), errors)
    		})
    		.await
    		.unwrap();
    
    		if !output.is_empty() {
    			msg.channel_id.say(&ctx, output).await.unwrap();
    		}
    
    		if let Some(fb) = &fb {
    			let buf = fb.as_png_vec();
    
    			msg.channel_id
    				.send_message(&ctx, |e| {
    					e.add_file(AttachmentType::Bytes {
    						data: Cow::Borrowed(&buf),
    						filename: "output.png".to_string(),
    					});
    
    					e
    				})
    				.await
    				.unwrap();
    		}
    
    		if let Some(e) = errors {
    			msg.channel_id.say(&ctx, e).await.unwrap();
    			if let Ok(react) = ReactionType::from_str("❌") {
    				let _ = msg.react(&ctx, react).await;
    			}
    		} else if let Ok(react) = ReactionType::from_str("✅") {
    			let _ = msg.react(&ctx, react).await;
    		}
    
    		if let Some(t) = thinking_react {
    			let _ = t.delete(&ctx).await;
    		}
    	}
    }
    
    #[async_trait]
    impl LineHandler for XbasicHandler {
    	async fn line(&self, ctx: &Context, msg: &Message, line: &str) {
    		self.interpret_line(msg, ctx, line).await;
    	}
    }