enable multiple pw games
This commit is contained in:
parent
addbe74222
commit
6f22aea8d1
8 changed files with 245 additions and 158 deletions
|
@ -8,9 +8,11 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
mozaic = { git = "https://github.com/ZeusWPI/MOZAICP" }
|
||||
tokio = "0.1.22"
|
||||
rand = { version = "0.6.5", default-features = true }
|
||||
futures = "0.1.28"
|
||||
|
||||
async-std = { version = "1.5.0", features = ["attributes"] }
|
||||
futures = { version = "0.3.1", features = ["executor", "thread-pool"] }
|
||||
|
||||
serde = "1.0.100"
|
||||
serde_derive = "1.0.100"
|
||||
serde_json = "1.0"
|
||||
|
|
|
@ -3,138 +3,124 @@ extern crate serde;
|
|||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
extern crate async_std;
|
||||
extern crate futures;
|
||||
extern crate mozaic;
|
||||
extern crate rand;
|
||||
extern crate tokio;
|
||||
|
||||
extern crate tracing;
|
||||
extern crate tracing_futures;
|
||||
extern crate tracing_subscriber;
|
||||
|
||||
use mozaic::errors;
|
||||
use mozaic::messaging::types::*;
|
||||
use std::env;
|
||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, time};
|
||||
|
||||
use tracing::{span, Level};
|
||||
use tracing_futures::Instrument;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
use mozaic::modules::types::*;
|
||||
use mozaic::modules::{game, StepLock};
|
||||
|
||||
use mozaic::modules::{game, Aggregator, Steplock};
|
||||
use futures::executor::ThreadPool;
|
||||
use futures::future::FutureExt;
|
||||
|
||||
use mozaic::graph;
|
||||
use mozaic::modules::*;
|
||||
|
||||
mod planetwars;
|
||||
|
||||
// Load the config and start the game.
|
||||
fn main() {
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let name = args[0].clone();
|
||||
match run(args) {
|
||||
match run(args).await {
|
||||
None => print_info(&name),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
use errors::Consumable;
|
||||
use mozaic::modules::util;
|
||||
use mozaic::modules::ConnectionManager;
|
||||
use mozaic::runtime::Broker;
|
||||
use rand::Rng;
|
||||
use std::collections::HashMap;
|
||||
fn build_builder(
|
||||
pool: ThreadPool,
|
||||
number_of_clients: u64,
|
||||
max_turns: u64,
|
||||
map: &str,
|
||||
location: &str,
|
||||
) -> game::Builder<planetwars::PlanetWarsGame> {
|
||||
let config = planetwars::Config {
|
||||
map_file: map.to_string(),
|
||||
max_turns: max_turns,
|
||||
};
|
||||
|
||||
fn print_info(name: &str) {
|
||||
println!(
|
||||
"Usage: {} map_location [number_of_clients [output [max_turns]]]",
|
||||
name
|
||||
);
|
||||
let game =
|
||||
planetwars::PlanetWarsGame::new(config.create_game(number_of_clients as usize), location);
|
||||
|
||||
let players: Vec<PlayerId> = (0..number_of_clients).collect();
|
||||
|
||||
game::Builder::new(players.clone(), game).with_step_lock(
|
||||
StepLock::new(players.clone(), pool.clone())
|
||||
.with_timeout(std::time::Duration::from_secs(1)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn run(args: Vec<String>) -> Option<()> {
|
||||
let subscriber = fmt::Subscriber::builder()
|
||||
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or(EnvFilter::from("info")))
|
||||
.without_time()
|
||||
.inherit_fields(true)
|
||||
async fn run(args: Vec<String>) -> Option<()> {
|
||||
let fut = graph::set_default();
|
||||
|
||||
let sub = FmtSubscriber::builder()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.finish();
|
||||
let _ = tracing::subscriber::set_global_default(subscriber);
|
||||
tracing::subscriber::set_global_default(sub).unwrap();
|
||||
|
||||
let addr = "127.0.0.1:9142".parse::<SocketAddr>().unwrap();
|
||||
|
||||
let manager_id: ReactorId = rand::thread_rng().gen();
|
||||
let welcomer_id: ReactorId = rand::thread_rng().gen();
|
||||
let aggregator_id: ReactorId = rand::thread_rng().gen();
|
||||
let steplock_id: ReactorId = rand::thread_rng().gen();
|
||||
|
||||
let map = args.get(1)?;
|
||||
let number_of_clients = args
|
||||
.get(2)
|
||||
.map(|x| x.parse().expect("Client number should be a number"))
|
||||
.unwrap_or(1);
|
||||
let location = args.get(3).map(|x| x.as_str()).unwrap_or("game.json");
|
||||
let max_turns = args
|
||||
let max_turns: u64 = args
|
||||
.get(4)
|
||||
.map(|x| x.parse().expect("Max turns should be a number"))
|
||||
.unwrap_or(500);
|
||||
|
||||
let ids: HashMap<util::Identifier, util::PlayerId> = (0..number_of_clients)
|
||||
.map(|x| (rand::thread_rng().gen::<u64>().into(), x.into()))
|
||||
.collect();
|
||||
let pool = ThreadPool::new().ok()?;
|
||||
pool.spawn_ok(fut.map(|_| ()));
|
||||
|
||||
let config = planetwars::Config {
|
||||
map_file: map.to_string(),
|
||||
max_turns: max_turns,
|
||||
};
|
||||
let game =
|
||||
planetwars::PlanetWarsGame::new(config.create_game(number_of_clients as usize), location);
|
||||
let (gmb, handle) = game::Manager::builder(pool.clone());
|
||||
let ep = TcpEndpoint::new(addr, pool.clone());
|
||||
let gmb = gmb.add_endpoint(ep, "TCP endpoint");
|
||||
let mut gm = gmb.build();
|
||||
|
||||
println!("Tokens:");
|
||||
let keys: Vec<u64> = ids.keys().map(|&x| x.into()).collect();
|
||||
for key in keys {
|
||||
println!("key {}", key);
|
||||
let game_builder = build_builder(pool.clone(), number_of_clients, max_turns, map, location);
|
||||
std::thread::sleep(time::Duration::from_millis(3000));
|
||||
|
||||
let mut current_game = gm.start_game(game_builder).await.unwrap();
|
||||
|
||||
loop {
|
||||
match gm.get_state(current_game).await {
|
||||
None => {
|
||||
println!("Game finished, let's play a new one");
|
||||
let game_builder =
|
||||
build_builder(pool.clone(), number_of_clients, max_turns, map, location);
|
||||
current_game = gm.start_game(game_builder).await.unwrap();
|
||||
}
|
||||
Some(state) => {
|
||||
println!("{:?}", state);
|
||||
}
|
||||
}
|
||||
std::thread::sleep(time::Duration::from_millis(3000));
|
||||
}
|
||||
|
||||
tokio::run(futures::lazy(move || {
|
||||
mozaic::graph::set_graph(mozaic::graph::Graph::new());
|
||||
let mut broker = Broker::new().unwrap();
|
||||
handle.await;
|
||||
|
||||
broker
|
||||
.spawn(
|
||||
welcomer_id.clone(),
|
||||
game::GameReactor::params(steplock_id.clone(), Box::new(game)),
|
||||
"Main",
|
||||
)
|
||||
.display();
|
||||
broker
|
||||
.spawn(
|
||||
steplock_id.clone(),
|
||||
Steplock::new(
|
||||
broker.clone(),
|
||||
ids.values().cloned().collect(),
|
||||
welcomer_id.clone(),
|
||||
aggregator_id.clone(),
|
||||
)
|
||||
.with_timeout(5000)
|
||||
.params(),
|
||||
"Steplock",
|
||||
)
|
||||
.display();
|
||||
broker
|
||||
.spawn(
|
||||
aggregator_id.clone(),
|
||||
Aggregator::params(manager_id.clone(), steplock_id.clone()),
|
||||
"Aggregator",
|
||||
)
|
||||
.display();
|
||||
broker
|
||||
.spawn(
|
||||
manager_id.clone(),
|
||||
ConnectionManager::params(broker.clone(), ids, aggregator_id.clone(), addr),
|
||||
"Connection Manager",
|
||||
)
|
||||
.display();
|
||||
|
||||
Ok(())
|
||||
}).instrument(span!(Level::TRACE, "main")),
|
||||
);
|
||||
std::thread::sleep(time::Duration::from_millis(100));
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn print_info(name: &str) {
|
||||
println!(
|
||||
"Usage: {} map_location [number_of_clients [output [max_turns]]]",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
use mozaic::modules::game;
|
||||
use mozaic::modules::types::{Data, HostMsg, PlayerMsg};
|
||||
|
||||
use serde_json;
|
||||
|
||||
|
@ -9,38 +9,52 @@ use std::fs::File;
|
|||
use std::io::Write;
|
||||
|
||||
mod pw_config;
|
||||
mod pw_serializer;
|
||||
mod pw_rules;
|
||||
mod pw_protocol;
|
||||
use pw_protocol::{ self as proto, CommandError };
|
||||
use pw_rules::Dispatch;
|
||||
mod pw_rules;
|
||||
mod pw_serializer;
|
||||
pub use pw_config::Config;
|
||||
use pw_protocol::{self as proto, CommandError};
|
||||
use pw_rules::Dispatch;
|
||||
|
||||
pub struct PlanetWarsGame {
|
||||
state: pw_rules::PlanetWars,
|
||||
planet_map: HashMap<String, usize>,
|
||||
log_file: File
|
||||
log_file: File,
|
||||
}
|
||||
|
||||
impl PlanetWarsGame {
|
||||
|
||||
pub fn new(state: pw_rules::PlanetWars, location: &str) -> Self {
|
||||
let planet_map = state.planets.iter().map(|p| (p.name.clone(), p.id)).collect();
|
||||
let planet_map = state
|
||||
.planets
|
||||
.iter()
|
||||
.map(|p| (p.name.clone(), p.id))
|
||||
.collect();
|
||||
let file = File::create(location).unwrap();
|
||||
|
||||
Self {
|
||||
state, planet_map,
|
||||
state,
|
||||
planet_map,
|
||||
log_file: file,
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_state(&mut self, were_alive: Vec<usize>, updates: &mut Vec<game::Update>, ) {
|
||||
fn dispatch_state(&mut self, were_alive: Vec<usize>, updates: &mut Vec<HostMsg>) {
|
||||
let state = pw_serializer::serialize(&self.state);
|
||||
write!(self.log_file, "{}\n", serde_json::to_string(&state).unwrap()).unwrap();
|
||||
write!(
|
||||
self.log_file,
|
||||
"{}\n",
|
||||
serde_json::to_string(&state).unwrap()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// println!("{}", serde_json::to_string(&state).unwrap());
|
||||
|
||||
for player in self.state.players.iter().filter(|p| were_alive.contains(&p.id)) {
|
||||
for player in self
|
||||
.state
|
||||
.players
|
||||
.iter()
|
||||
.filter(|p| were_alive.contains(&p.id))
|
||||
{
|
||||
let state = pw_serializer::serialize_rotated(&self.state, player.id);
|
||||
let state = if player.alive && !self.state.is_finished() {
|
||||
proto::ServerMessage::GameState(state)
|
||||
|
@ -48,64 +62,80 @@ impl PlanetWarsGame {
|
|||
proto::ServerMessage::FinalState(state)
|
||||
};
|
||||
|
||||
updates.push(
|
||||
game::Update::Player((player.id as u64).into(), serde_json::to_vec(&state).unwrap())
|
||||
);
|
||||
updates.push(HostMsg::Data(
|
||||
Data {
|
||||
value: serde_json::to_string(&state).unwrap(),
|
||||
},
|
||||
Some(player.id as u64),
|
||||
));
|
||||
|
||||
if !player.alive || self.state.is_finished() {
|
||||
println!("Kicking player {}", player.id);
|
||||
updates.push(game::Update::Kick((player.id as u64).into()));
|
||||
updates.push(HostMsg::Kick(player.id as u64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_commands<'a>(&mut self, turns: Vec<game::PlayerTurn<'a>>, updates: &mut Vec<game::Update>) {
|
||||
for (player_id, command) in turns.into_iter() {
|
||||
let player_num: usize = (*player_id).try_into().unwrap();
|
||||
let action = proto::ServerMessage::PlayerAction(self.execute_action(player_num, command));
|
||||
let serialized_action = serde_json::to_vec(&action).unwrap();
|
||||
updates.push(game::Update::Player(player_id, serialized_action));
|
||||
fn execute_commands<'a>(&mut self, turns: Vec<PlayerMsg>, updates: &mut Vec<HostMsg>) {
|
||||
for PlayerMsg { id, data } in turns.into_iter() {
|
||||
let player_num: usize = (id).try_into().unwrap();
|
||||
let action = proto::ServerMessage::PlayerAction(self.execute_action(player_num, data));
|
||||
let serialized_action = serde_json::to_string(&action).unwrap();
|
||||
updates.push(HostMsg::Data(
|
||||
Data {
|
||||
value: serialized_action,
|
||||
},
|
||||
Some(id),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_action<'a>(&mut self, player_num: usize, turn: game::Turn<'a>) -> proto::PlayerAction {
|
||||
fn execute_action<'a>(&mut self, player_num: usize, turn: Option<Data>) -> proto::PlayerAction {
|
||||
let turn = match turn {
|
||||
game::Turn::Timeout => return proto::PlayerAction::Timeout,
|
||||
game::Turn::Action(bytes) => bytes,
|
||||
None => return proto::PlayerAction::Timeout,
|
||||
Some(turn) => turn.value,
|
||||
};
|
||||
|
||||
let action: proto::Action = match serde_json::from_slice(&turn) {
|
||||
let action: proto::Action = match serde_json::from_str(&turn) {
|
||||
Err(err) => return proto::PlayerAction::ParseError(err.to_string()),
|
||||
Ok(action) => action,
|
||||
};
|
||||
|
||||
let commands = action.commands.into_iter().map(|command| {
|
||||
match self.check_valid_command(player_num, &command) {
|
||||
let commands = action
|
||||
.commands
|
||||
.into_iter()
|
||||
.map(
|
||||
|command| match self.check_valid_command(player_num, &command) {
|
||||
Ok(dispatch) => {
|
||||
self.state.dispatch(&dispatch);
|
||||
proto::PlayerCommand {
|
||||
command,
|
||||
error: None,
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
proto::PlayerCommand {
|
||||
}
|
||||
Err(error) => proto::PlayerCommand {
|
||||
command,
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
},
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
return proto::PlayerAction::Commands(commands);
|
||||
}
|
||||
|
||||
fn check_valid_command(&self, player_num: usize, mv: &proto::Command) -> Result<Dispatch, CommandError> {
|
||||
let origin_id = *self.planet_map
|
||||
fn check_valid_command(
|
||||
&self,
|
||||
player_num: usize,
|
||||
mv: &proto::Command,
|
||||
) -> Result<Dispatch, CommandError> {
|
||||
let origin_id = *self
|
||||
.planet_map
|
||||
.get(&mv.origin)
|
||||
.ok_or(CommandError::OriginDoesNotExist)?;
|
||||
|
||||
let target_id = *self.planet_map
|
||||
let target_id = *self
|
||||
.planet_map
|
||||
.get(&mv.destination)
|
||||
.ok_or(CommandError::DestinationDoesNotExist)?;
|
||||
|
||||
|
@ -129,8 +159,14 @@ impl PlanetWarsGame {
|
|||
}
|
||||
}
|
||||
|
||||
impl game::GameController for PlanetWarsGame {
|
||||
fn step<'a>(&mut self, turns: Vec<game::PlayerTurn<'a>>) -> Vec<game::Update> {
|
||||
impl game::Controller for PlanetWarsGame {
|
||||
fn start(&mut self) -> Vec<HostMsg> {
|
||||
let mut updates = Vec::new();
|
||||
self.dispatch_state(self.state.living_players(), &mut updates);
|
||||
updates
|
||||
}
|
||||
|
||||
fn step(&mut self, turns: Vec<PlayerMsg>) -> Vec<HostMsg> {
|
||||
let mut updates = Vec::new();
|
||||
|
||||
let alive = self.state.living_players();
|
||||
|
@ -143,4 +179,8 @@ impl game::GameController for PlanetWarsGame {
|
|||
|
||||
updates
|
||||
}
|
||||
|
||||
fn is_done(&mut self) -> bool {
|
||||
self.state.is_finished()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
|
||||
use serde_json;
|
||||
|
||||
|
@ -17,7 +17,10 @@ impl Config {
|
|||
pub fn create_game(&self, clients: usize) -> PlanetWars {
|
||||
let planets = self.load_map(clients);
|
||||
let players = (0..clients)
|
||||
.map(|client_id| Player { id: client_id, alive: true })
|
||||
.map(|client_id| Player {
|
||||
id: client_id,
|
||||
alive: true,
|
||||
})
|
||||
.collect();
|
||||
|
||||
PlanetWars {
|
||||
|
@ -33,7 +36,8 @@ impl Config {
|
|||
fn load_map(&self, num_players: usize) -> Vec<Planet> {
|
||||
let map = self.read_map().expect("[PLANET_WARS] reading map failed");
|
||||
|
||||
return map.planets
|
||||
return map
|
||||
.planets
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(num, planet)| {
|
||||
|
@ -62,7 +66,8 @@ impl Config {
|
|||
y: planet.y,
|
||||
fleets: fleets,
|
||||
};
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn read_map(&self) -> io::Result<Map> {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/// The planet wars game rules.
|
||||
pub struct PlanetWars {
|
||||
pub players: Vec<Player>,
|
||||
|
@ -49,16 +48,12 @@ pub struct Dispatch {
|
|||
}
|
||||
|
||||
impl PlanetWars {
|
||||
|
||||
pub fn dispatch(&mut self, dispatch: &Dispatch) {
|
||||
let distance = self.planets[dispatch.origin].distance(
|
||||
&self.planets[dispatch.target]
|
||||
);
|
||||
let distance = self.planets[dispatch.origin].distance(&self.planets[dispatch.target]);
|
||||
|
||||
let origin = &mut self.planets[dispatch.origin];
|
||||
origin.fleets[0].ship_count -= dispatch.ship_count;
|
||||
|
||||
|
||||
let expedition = Expedition {
|
||||
id: self.expedition_num,
|
||||
origin: dispatch.origin,
|
||||
|
@ -136,17 +131,13 @@ impl PlanetWars {
|
|||
}
|
||||
|
||||
pub fn living_players(&self) -> Vec<usize> {
|
||||
self.players.iter().filter_map(|p| {
|
||||
if p.alive {
|
||||
Some(p.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect()
|
||||
self.players
|
||||
.iter()
|
||||
.filter_map(|p| if p.alive { Some(p.id) } else { None })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Planet {
|
||||
pub fn owner(&self) -> Option<usize> {
|
||||
self.fleets.first().and_then(|f| f.owner)
|
||||
|
@ -176,7 +167,8 @@ impl Planet {
|
|||
// winner.ship_count -= second_largest.ship_count, but this does not
|
||||
// allow for simple customizations (such as changing combat balance).
|
||||
|
||||
self.fleets.sort_by(|a, b| a.ship_count.cmp(&b.ship_count).reverse());
|
||||
self.fleets
|
||||
.sort_by(|a, b| a.ship_count.cmp(&b.ship_count).reverse());
|
||||
while self.fleets.len() > 1 {
|
||||
let fleet = self.fleets.pop().unwrap();
|
||||
// destroy some ships
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
use super::pw_rules::{PlanetWars, Planet, Expedition};
|
||||
use super::pw_protocol as proto;
|
||||
use super::pw_rules::{Expedition, Planet, PlanetWars};
|
||||
|
||||
/// Serialize given gamestate
|
||||
pub fn serialize(state: &PlanetWars) -> proto::State {
|
||||
|
@ -28,12 +27,14 @@ impl<'a> Serializer<'a> {
|
|||
|
||||
fn serialize_state(&self) -> proto::State {
|
||||
proto::State {
|
||||
planets: self.state
|
||||
planets: self
|
||||
.state
|
||||
.planets
|
||||
.iter()
|
||||
.map(|planet| self.serialize_planet(planet))
|
||||
.collect(),
|
||||
expeditions: self.state
|
||||
expeditions: self
|
||||
.state
|
||||
.expeditions
|
||||
.iter()
|
||||
.map(|exp| self.serialize_expedition(exp))
|
||||
|
|
58
client/bot.py
Executable file
58
client/bot.py
Executable file
|
@ -0,0 +1,58 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
import socket, sys, subprocess, argparse, io, threading, json
|
||||
|
||||
|
||||
def execute(cmd):
|
||||
popen = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
yield(popen.stdin)
|
||||
|
||||
for stdout_line in iter(popen.stdout.readline, "\n"):
|
||||
yield stdout_line
|
||||
popen.stdout.close()
|
||||
return_code = popen.wait()
|
||||
if return_code:
|
||||
raise subprocess.CalledProcessError(return_code, cmd)
|
||||
|
||||
def connect(host, port, id):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((host, port))
|
||||
s.sendall(f"{id}\n".encode("utf8"))
|
||||
return s
|
||||
|
||||
def handle_input(it, socket):
|
||||
for line in it:
|
||||
socket.sendall(line.encode('utf8'))
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Run MOZAIC bot')
|
||||
parser.add_argument('--id', '-i', required=True,
|
||||
help='The bot\'s ID')
|
||||
parser.add_argument('--host', default="localhost",
|
||||
help='What host to connect to')
|
||||
parser.add_argument('--port', '-p', default=6666, type=int,
|
||||
help='What port to connect to')
|
||||
parser.add_argument('arguments', nargs=argparse.REMAINDER,
|
||||
help='How to run the bot')
|
||||
args = parser.parse_args()
|
||||
|
||||
sock = connect(args.host, args.port, args.id)
|
||||
f = sock.makefile("rw")
|
||||
|
||||
it = execute(args.arguments)
|
||||
stdin = next(it)
|
||||
|
||||
threading.Thread(target=lambda: handle_input(it, sock), daemon=True).start()
|
||||
|
||||
line = f.readline()
|
||||
while line:
|
||||
content = json.loads(line)
|
||||
if content["type"] == "game_state":
|
||||
stdin.write(json.dumps(content["content"])+"\n")
|
||||
stdin.flush()
|
||||
line = f.readline()
|
||||
|
||||
print(content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
3
client/run2.sh
Executable file
3
client/run2.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
python bot.py -p 9142 -i $1 python simple.py
|
Loading…
Reference in a new issue