summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorstale <redkugelblitzin@gmail.com>2022-08-21 04:53:12 -0300
committerstale <redkugelblitzin@gmail.com>2022-08-21 04:53:12 -0300
commit5e207ab59b3d5ce2ce316d8727886aa66147f758 (patch)
tree90ede1c22d238bf3c8bf6615701bb6803147588f /src
parente28498c4953f2da5f56a41f3123a413efa9d9a25 (diff)
questioning tiles and irc bot mess
Diffstat (limited to 'src')
-rw-r--r--src/conn.rs62
-rw-r--r--src/ircbot.rs79
-rw-r--r--src/main.rs37
-rw-r--r--src/minesweeper.rs42
-rw-r--r--src/types.rs5
5 files changed, 175 insertions, 50 deletions
diff --git a/src/conn.rs b/src/conn.rs
index 2b6ee80..c8a0a82 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -7,6 +7,7 @@ use tokio::sync::RwLock;
use futures::{SinkExt, TryStreamExt, StreamExt, stream::SplitStream};
use warp::ws::{ WebSocket, Message };
use crate::livepos;
+use crate::ircbot;
pub async fn setup_conn(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc<RwLock<Room>>), max_in: usize) {
let (room_id, room) = rinfo;
@@ -61,9 +62,9 @@ pub async fn setup_conn(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc<
pub async fn drive_conn(conn: (Conn, SplitStream<WebSocket>), rinfo: (RoomId, Arc<RwLock<Room>>), max_in: usize) {
let (conn, mut incoming) = conn;
let (room_id, room) = rinfo;
- let (players, cmd_tx, pos_tx, room_conf) = {
+ let (players, cmd_tx, pos_tx, irc_tx, room_conf) = {
let room = room.read().await;
- (room.players.clone(), room.cmd_stream.clone(), room.pos_stream.clone(), room.conf.clone())
+ (room.players.clone(), room.cmd_stream.clone(), room.pos_stream.clone(), room.irc_stream.clone(), room.conf.clone())
};
while let Ok(cmd) = incoming.try_next().await {
if let Some(cmd) = cmd {
@@ -144,33 +145,42 @@ pub async fn drive_conn(conn: (Conn, SplitStream<WebSocket>), rinfo: (RoomId, Ar
if n.is_empty() { def } else { n }
}
};
- println!("{room_id} I: registered \"{name}@{}\"", conn.addr);
- drop(players_lock);
- let uid = {
- // new scope cuz paranoid bout deadlocks
- room.write().await.players.write().await.insert_conn(conn.clone(), name.clone(), clr)
- };
- let players_lock = players.read().await;
- let me = players_lock.get(&conn.addr).unwrap();
- conn.tx.send(Message::text(format!("regack {} {} {} {}",
- room_conf.name.replace(' ', "&nbsp;"), name.replace(' ', "&nbsp;"), uid, room_conf.board_conf))
- ).expect("couldn't send register ack");
- {
- let msg = Message::text(format!("players {}",
- jsonenc_players(players_lock.values())
- .expect("couldn't JSONify players")));
- for p in players_lock.values() {
- if let Err(e) = p.conn.tx.send(msg.clone()) {
- println!("{room_id} E: couldn't dump players for {me}: {e}");
+ let (nameq_tx, nameq_rx) = tokio::sync::oneshot::channel();
+ irc_tx.send(ircbot::IrcCmd::NameTakenQuery(name.clone(), nameq_tx)).expect("couldn't check for name collision");
+
+ if nameq_rx.await.unwrap() {
+ println!("{room_id} I: name collision \"{name}@{}\"", conn.addr);
+ conn.tx.send(Message::text("namecoll")).expect("couldn't send name collision report");
+ } else {
+ println!("{room_id} I: registered \"{name}@{}\"", conn.addr);
+ drop(players_lock);
+ let uid = {
+ // new scope cuz paranoid bout deadlocks
+ room.write().await.players.write().await.insert_conn(conn.clone(), name.clone(), clr)
+ };
+ let players_lock = players.read().await;
+ let me = players_lock.get(&conn.addr).unwrap();
+ conn.tx.send(Message::text(format!("regack {} {} {} {}",
+ room_conf.name.replace(' ', "&nbsp;"), name.replace(' ', "&nbsp;"), uid, room_conf.board_conf))
+ ).expect("couldn't send register ack");
+
+ {
+ let msg = Message::text(format!("players {}",
+ jsonenc_players(players_lock.values())
+ .expect("couldn't JSONify players")));
+ for p in players_lock.values() {
+ if let Err(e) = p.conn.tx.send(msg.clone()) {
+ println!("{room_id} E: couldn't dump players for {me}: {e}");
+ }
}
}
- }
- if let Err(e) = pos_tx.send(livepos::Req { id: uid, data: livepos::ReqData::StateDump }) {
- println!("{room_id} E: couldn't request position dump for {me}: {e}");
- }
- if let Err(e) = cmd_tx.send(MetaMove::StateDump) {
- println!("{room_id} E: couldn't request game dump for {me}: {e}");
+ if let Err(e) = pos_tx.send(livepos::Req { id: uid, data: livepos::ReqData::StateDump }) {
+ println!("{room_id} E: couldn't request position dump for {me}: {e}");
+ }
+ if let Err(e) = cmd_tx.send(MetaMove::StateDump) {
+ println!("{room_id} E: couldn't request game dump for {me}: {e}");
+ }
}
}
}
diff --git a/src/ircbot.rs b/src/ircbot.rs
new file mode 100644
index 0000000..b61e951
--- /dev/null
+++ b/src/ircbot.rs
@@ -0,0 +1,79 @@
+//use irc::client::prelude::*;
+use crate::types::{RoomConf, CmdTx};
+use tokio::sync::mpsc as tokio_mpsc;
+use serde::Deserialize;
+//use futures::prelude::*;
+
+#[derive(Debug)]
+pub enum IrcCmd {
+ NameTakenQuery(String, tokio::sync::oneshot::Sender<bool>),
+ GameWin(String),
+ GameLose(String),
+}
+
+pub type IrcCmdTx = tokio_mpsc::UnboundedSender<IrcCmd>;
+
+#[derive(Deserialize, Clone)]
+pub struct IrcConf {
+ pub server: String,
+ pub port: u16,
+}
+
+pub async fn manage_irc_channel(_irc_conf: IrcConf, _room_conf: RoomConf, _game_tx: CmdTx, mut irc_rx: tokio_mpsc::UnboundedReceiver<IrcCmd>) {
+ // turns out none of the irc libs i tried worked and i lost interest
+ //
+ // let channel_name = format!("#mines-{}", room_conf.name);
+ // let bot_name = format!("mines-bot-{}", room_conf.name);
+ // let config = Config {
+ // nickname: Some(bot_name.clone()),
+ // username: Some(bot_name.clone()),
+ // realname: Some(bot_name.clone()),
+ // server: Some(irc_conf.server),
+ // port: Some(irc_conf.port),
+ // encoding: Some("UTF-8".to_string()),
+ // channels: vec![channel_name.clone()],
+ // umodes: Some("+B-x".to_string()),
+ // user_info: Some("websweeper channel manager bot".to_string()),
+ // use_tls: Some(true),
+ // ping_time: Some(20),
+ // ping_timeout: Some(15),
+ // ..Default::default()
+ // };
+
+ // let mut client = Client::from_config(config).await.expect("couldn't create an irc client");
+ // client.identify().expect("couldn't identify irc bot");
+
+ // println!("irc bot {:#?}", client);
+
+ // if !room_conf.public {
+ // client.send_mode(&channel_name, &[Mode::Plus(ChannelMode::Secret, None)]).expect("couldn't set irc channel mode");
+ // }
+ // client.send_mode(&channel_name,
+ // &[Mode::Plus(ChannelMode::Limit, Some(room_conf.player_cap.to_string()))]
+ // ).expect("couldn't set irc channel mode");
+
+ while let Some(req) = irc_rx.recv().await {
+ match req {
+ IrcCmd::NameTakenQuery(_nick, res_tx) => {
+ // let taken: bool = client.list_users(&channel_name)
+ // .and_then(|userlist| {
+ // userlist.iter().position(|u| u.get_nickname() == nick)
+ // }).is_some();
+ // res_tx.send(taken).unwrap();
+ res_tx.send(false).unwrap();
+ },
+ IrcCmd::GameWin(_nick) => {
+ // println!("irc {nick} win");
+ // if let Err(e) = client.send(Command::PRIVMSG(channel_name.clone(), format!("You win! {nick} made the winning move."))) {
+ // println!("couldn't send irc win message: {e}");
+ // }
+ },
+ IrcCmd::GameLose(_nick) => {
+ // println!("irc {nick} lose");
+ // if let Err(e) = client.send(Command::PRIVMSG(channel_name.clone(), format!("You win! {nick} made the winning move."))) {
+ // println!("couldn't send irc lose message: {e}");
+ // }
+ },
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 0c81844..29a847c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,6 +14,7 @@ mod types;
mod livepos;
mod conn;
mod minesweeper;
+mod ircbot;
use types::*;
const CONF_FILE: &str = "./conf.json";
@@ -42,6 +43,7 @@ struct Conf {
pub paths: ConfPaths,
pub server: ConfServer,
pub limits: ConfLimits,
+ pub irc: ircbot::IrcConf,
}
fn main() -> Result<(), Box<dyn Error>> {
@@ -215,7 +217,7 @@ async fn tokio_main(conf: Conf) -> Result<(), Box<dyn Error>> {
// If a move is made, broadcast new board, else just send current board
type MoveStreamHandles = (tokio::sync::mpsc::UnboundedSender<MetaMove>, tokio::sync::mpsc::UnboundedReceiver<MetaMove>);
-async fn gameloop(moves: MoveStreamHandles, players: Arc<RwLock<PlayerMap>>, bconf: minesweeper::BoardConf) {
+async fn gameloop(moves: MoveStreamHandles, irc_tx: ircbot::IrcCmdTx, players: Arc<RwLock<PlayerMap>>, bconf: minesweeper::BoardConf) {
// FIXME: push new board if and only if there aren't any remaining commands in the queue
use minesweeper::*;
use flate2::{ Compression, write::DeflateEncoder };
@@ -245,10 +247,20 @@ async fn gameloop(moves: MoveStreamHandles, players: Arc<RwLock<PlayerMap>>, bco
board_encoder.write_all(&game.board.render()).unwrap();
let compressed_board = board_encoder.finish().unwrap();
let mut reply = vec![Message::binary(compressed_board)];
- let lpname = final_player_name.as_deref().unwrap_or("unknown player").replace(' ', "&nbsp");
+ let lpname = final_player_name.as_deref().unwrap_or("unknown player");
match game.phase {
- Phase::Win => { reply.push(Message::text(format!("win {lpname}"))); },
- Phase::Die => { reply.push(Message::text(format!("lose {lpname}"))); },
+ Phase::Win => {
+ reply.push(Message::text(format!("win {lpname}")));
+ if let Err(e) = irc_tx.send(ircbot::IrcCmd::GameWin(lpname.to_string())) {
+ println!("couldn't send irc win message: {e}");
+ }
+ },
+ Phase::Die => {
+ reply.push(Message::text(format!("lose {lpname}")));
+ if let Err(e) = irc_tx.send(ircbot::IrcCmd::GameLose(lpname.to_string())) {
+ println!("couldn't send irc lose message: {e}");
+ }
+ },
_ => (),
}
let peers = players.read().await;
@@ -336,18 +348,21 @@ fn room_from_form(uid: RoomId, rinfo: &HashMap<String,String>, conf: &Conf) -> R
let players = Arc::new(RwLock::new(PlayerMap::default()));
- let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
- let game_handle = tokio::spawn(gameloop((cmd_tx.clone(), cmd_rx), players.clone(), board_conf));
-
- let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel();
- let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx));
-
let room_conf = RoomConf {
name,
player_cap: limit,
public,
board_conf,
};
+
+ let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
+ let (irc_tx, irc_rx) = tokio::sync::mpsc::unbounded_channel();
+ let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel();
+
+ let irc_handle = tokio::spawn(ircbot::manage_irc_channel(conf.irc.clone(), room_conf.clone(), cmd_tx.clone(), irc_rx));
+ let game_handle = tokio::spawn(gameloop((cmd_tx.clone(), cmd_rx), irc_tx.clone(), players.clone(), board_conf));
+ let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx));
+
Ok((Room {
conf: room_conf,
players,
@@ -355,6 +370,8 @@ fn room_from_form(uid: RoomId, rinfo: &HashMap<String,String>, conf: &Conf) -> R
cmd_stream: cmd_tx,
livepos_driver: livepos_handle,
pos_stream: pos_tx,
+ irc_driver: irc_handle,
+ irc_stream: irc_tx,
}, public))
} else { Err(warp::reject::custom(BadFormData)) }
}
diff --git a/src/minesweeper.rs b/src/minesweeper.rs
index 1e95f0d..c733d19 100644
--- a/src/minesweeper.rs
+++ b/src/minesweeper.rs
@@ -7,10 +7,13 @@ use serde::Serialize;
const HIDDEN_BIT: u8 = 1 << 7;
pub const FLAGGED_BIT: u8 = 1 << 6;
-const CORRECT_BIT: u8 = 1 << 5; // grading for a rightly flagged mine
+const SPECIAL_BIT: u8 = 1 << 5; // grading for a rightly flagged mine, or the question flag
// all the bits that aren't flags
-const TILE_NUMBITS: u8 = !(HIDDEN_BIT | FLAGGED_BIT | CORRECT_BIT);
-const MINED: u8 = HIDDEN_BIT | TILE_NUMBITS;
+const NUMBITS: u8 = !(HIDDEN_BIT | FLAGGED_BIT | SPECIAL_BIT);
+const MINED: u8 = HIDDEN_BIT | NUMBITS;
+const QUESTION: u8 = FLAGGED_BIT | SPECIAL_BIT;
+const CORRECT: u8 = MINED | SPECIAL_BIT;
+
#[derive(PartialEq)]
pub enum Phase {
SafeFirstMove,
@@ -182,7 +185,9 @@ impl Board {
while let Some(pos) = queue.pop() {
let off = pos.rel_offset_unchecked(&self);
let c = &mut self.data[off];
- if *c & HIDDEN_BIT > 0 {
+ // don't reveal the already revealed or the flagged, but reveal the questionings
+ let unrevealable = (*c & FLAGGED_BIT > 0) ^ (*c & SPECIAL_BIT > 0);
+ if *c & HIDDEN_BIT > 0 && !unrevealable {
*c = unhide(*c);
self.hidden_tiles -= 1;
if is_mine(*c) { return true; }
@@ -198,7 +203,7 @@ impl Board {
if 1 <= count && count <= 8 {
let mut neighs = self.neighs(pos);
let total_neighs = neighs.len();
- neighs.retain(|pos| self.data[pos.rel_offset_unchecked(&self)] & FLAGGED_BIT == 0);
+ neighs.retain(|pos| self.data[pos.rel_offset_unchecked(&self)] & (FLAGGED_BIT | SPECIAL_BIT) != FLAGGED_BIT);
if (total_neighs - neighs.len()) == count {
for pos in neighs.iter() {
if self.flood_reveal(*pos) {
@@ -223,14 +228,23 @@ impl Board {
pub fn grade(&mut self) {
for i in &mut self.data {
- if *i == TILE_NUMBITS | FLAGGED_BIT | HIDDEN_BIT {
- *i |= CORRECT_BIT;
+ if *i == MINED | FLAGGED_BIT {
+ *i = CORRECT;
}
}
}
pub fn flag(&mut self, pos: BoardPos) {
if let Some(off) = pos.rel_offset(&self) {
- self.data[off] ^= FLAGGED_BIT;
+ const TOPBIT_MASK: u8 = !(NUMBITS | HIDDEN_BIT);
+ let c = &mut self.data[off];
+ if *c & HIDDEN_BIT > 0 {
+ let new_topbits = match *c & (TOPBIT_MASK) {
+ FLAGGED_BIT => QUESTION,
+ QUESTION => 0,
+ _ => FLAGGED_BIT,
+ } | HIDDEN_BIT;
+ *c = (*c & NUMBITS) | new_topbits;
+ }
}
}
@@ -240,13 +254,15 @@ impl Board {
for x in 0..self.width.get() {
let pos: BoardPos = (x,y).try_into().unwrap();
let c = &self.data[pos.rel_offset_unchecked(&self)];
+ const QUESTION_MASK: u8 = SPECIAL_BIT | FLAGGED_BIT;
match *c {
0 => ret.push(b' '),
_ if *c <= 8 => ret.push(b'0' + c),
- _ if (*c & CORRECT_BIT) > 0 => ret.push(b'C'),
+ _ if (*c & QUESTION_MASK) == QUESTION_MASK => ret.push(b'Q'),
+ _ if (*c & SPECIAL_BIT) > 0 => ret.push(b'C'),
_ if (*c & FLAGGED_BIT) > 0 => ret.push(b'F'),
_ if (*c & HIDDEN_BIT) > 0 => ret.push(b'#'),
- _ if *c == TILE_NUMBITS => ret.push(b'O'),
+ _ if *c == NUMBITS => ret.push(b'O'),
_ => ret.push(b'?'),
}
}
@@ -268,7 +284,7 @@ impl Board {
let vacant_pos = {
let v = self.data.iter()
.enumerate()
- .filter(|(_,val)| (*val & TILE_NUMBITS) != TILE_NUMBITS)
+ .filter(|(_,val)| (*val & NUMBITS) != NUMBITS)
.map(|(p,_)| p)
.next()
.unwrap(); // there must be at least one
@@ -315,10 +331,10 @@ impl<T: TryInto<u32>> TryFrom<(T,T)> for BoardPos {
}
pub fn is_mine(v: u8) -> bool {
- (v & TILE_NUMBITS) == TILE_NUMBITS
+ (v & NUMBITS) == NUMBITS
}
pub fn unhide(tile: u8) -> u8 {
- tile & !(HIDDEN_BIT | FLAGGED_BIT)
+ tile & NUMBITS
}
diff --git a/src/types.rs b/src/types.rs
index a8a7742..b2bc93e 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -4,7 +4,7 @@ use std::{
sync::{
Arc,
atomic::{ AtomicUsize, Ordering },
- },
+},
fmt::Display,
ops::{ Deref, DerefMut },
num::NonZeroUsize,
@@ -14,6 +14,7 @@ use tokio::sync::RwLock;
use serde::Serialize;
use crate::minesweeper;
use crate::livepos;
+use crate::ircbot;
#[derive(Debug, Serialize, Clone)]
pub struct RoomConf {
@@ -30,6 +31,8 @@ pub struct Room {
pub cmd_stream: CmdTx,
pub livepos_driver: tokio::task::JoinHandle<()>,
pub pos_stream: tokio::sync::mpsc::UnboundedSender<livepos::Req>,
+ pub irc_driver: tokio::task::JoinHandle<()>,
+ pub irc_stream: tokio::sync::mpsc::UnboundedSender<ircbot::IrcCmd>,
}
#[derive(Debug)]