Show players in lobby
This commit is contained in:
parent
c64535675f
commit
86ffa9726c
14 changed files with 325 additions and 126 deletions
|
@ -3,6 +3,7 @@
|
|||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
extern crate async_std;
|
||||
|
@ -76,7 +77,7 @@ async fn main() {
|
|||
|
||||
let pool = ThreadPool::new().unwrap();
|
||||
pool.spawn_ok(fut.map(|_| ()));
|
||||
let gm = create_game_manager("0.0.0.0:9142", pool.clone());
|
||||
let gm = create_game_manager("0.0.0.0:9142", pool.clone()).await;
|
||||
|
||||
|
||||
let mut routes = Vec::new();
|
||||
|
@ -98,12 +99,12 @@ async fn main() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager {
|
||||
async fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager {
|
||||
let addr = tcp.parse::<SocketAddr>().unwrap();
|
||||
let (gmb, handle) = game::Manager::builder(pool.clone());
|
||||
pool.spawn_ok(handle.map(|_| ()));
|
||||
let ep = TcpEndpoint::new(addr, pool.clone());
|
||||
|
||||
let gmb = gmb.add_endpoint(ep, "TCP endpoint");
|
||||
gmb.build()
|
||||
gmb.build("games.ini", pool).await.unwrap()
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ pub struct PlanetWarsGame {
|
|||
log_file: File,
|
||||
turns: u64,
|
||||
name: String,
|
||||
logged: bool,
|
||||
}
|
||||
|
||||
impl PlanetWarsGame {
|
||||
|
@ -47,7 +46,6 @@ impl PlanetWarsGame {
|
|||
log_file: file,
|
||||
turns: 0,
|
||||
name: name.to_string(),
|
||||
logged: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,8 +167,8 @@ impl PlanetWarsGame {
|
|||
}
|
||||
}
|
||||
|
||||
use ini::Ini;
|
||||
use std::fs::OpenOptions;
|
||||
use serde_json::Value;
|
||||
|
||||
impl game::Controller for PlanetWarsGame {
|
||||
fn start(&mut self) -> Vec<HostMsg> {
|
||||
let mut updates = Vec::new();
|
||||
|
@ -194,31 +192,25 @@ impl game::Controller for PlanetWarsGame {
|
|||
updates
|
||||
}
|
||||
|
||||
fn is_done(&mut self) -> bool {
|
||||
fn is_done(&mut self) -> Option<Value> {
|
||||
if self.state.is_finished() {
|
||||
if !self.logged {
|
||||
let mut f = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("games.ini")
|
||||
{
|
||||
Err(_) => return true,
|
||||
Ok(f) => f,
|
||||
};
|
||||
|
||||
let mut conf = Ini::new();
|
||||
conf.with_section(Some(self.log_file_loc.clone()))
|
||||
.set("name", &self.name)
|
||||
.set("turns", format!("{}", self.turns));
|
||||
|
||||
conf.write_to(&mut f).unwrap();
|
||||
|
||||
self.logged = true;
|
||||
}
|
||||
|
||||
true
|
||||
Some(json!({
|
||||
"winners": self.state.living_players(),
|
||||
"turns": self.state.turn_num,
|
||||
"name": self.name.clone(),
|
||||
"file": self.log_file_loc.clone(),
|
||||
}))
|
||||
} else {
|
||||
false
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FinishedState {
|
||||
pub winners: Vec<u64>,
|
||||
pub turns: u64,
|
||||
pub name: String,
|
||||
pub file: String,
|
||||
pub players: Vec<(u64, String)>,
|
||||
}
|
||||
|
|
|
@ -116,16 +116,22 @@ async fn game_post(game_req: Json<GameReq>, tp: State<'_, ThreadPool>, gm: State
|
|||
let game_id = gm.start_game(game).await.unwrap();
|
||||
state.add_game(game_req.name.clone(), game_id);
|
||||
|
||||
if let Some(conns) = gm.get_state(game_id).await {
|
||||
match gm.get_state(game_id).await {
|
||||
Some(Ok(conns)) => {
|
||||
let players: Vec<u64> = conns.iter().map(|conn| match conn {
|
||||
Connect::Waiting(_, key) => *key,
|
||||
_ => 0,
|
||||
}).collect();
|
||||
|
||||
Ok(Json(GameRes { players }))
|
||||
} else {
|
||||
},
|
||||
Some(Err(v)) => {
|
||||
Err(serde_json::to_string(&v).unwrap())
|
||||
},
|
||||
None => {
|
||||
Err(String::from("Fuck the world"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fuel(routes: &mut Vec<Route>) {
|
||||
|
|
|
@ -9,12 +9,67 @@ pub struct Map {
|
|||
url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GameState {
|
||||
#[derive(Serialize, Eq, PartialEq)]
|
||||
pub struct PlayerStatus {
|
||||
waiting: bool,
|
||||
connected: bool,
|
||||
reconnecting: bool,
|
||||
value: String,
|
||||
}
|
||||
impl From<Connect> for PlayerStatus {
|
||||
fn from(value: Connect) -> Self {
|
||||
match value {
|
||||
Connect::Connected(_, name) => PlayerStatus { waiting: false, connected: true, reconnecting: false, value: name },
|
||||
Connect::Reconnecting(_, name) => PlayerStatus { waiting: false, connected: true, reconnecting: true, value: name },
|
||||
Connect::Waiting(_, key) => PlayerStatus { waiting: true, connected: false, reconnecting: false, value: format!("Key: {}", key) },
|
||||
_ => panic!("No playerstatus possible from Connect::Request"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Eq, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum GameState {
|
||||
Finished {
|
||||
name: String,
|
||||
finished: bool,
|
||||
turns: Option<u64>,
|
||||
players: Vec<String>,
|
||||
map: String,
|
||||
players: Vec<(String, bool)>,
|
||||
turns: u64,
|
||||
},
|
||||
Playing {
|
||||
name: String,
|
||||
map: String,
|
||||
players: Vec<PlayerStatus>,
|
||||
connected: usize,
|
||||
total: usize,
|
||||
}
|
||||
}
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
impl PartialOrd for GameState {
|
||||
fn partial_cmp(&self, other: &GameState) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for GameState {
|
||||
fn cmp(&self, other: &GameState) -> Ordering {
|
||||
match self {
|
||||
GameState::Finished { name, .. } => {
|
||||
match other {
|
||||
GameState::Finished { name: _name, .. } => name.cmp(_name),
|
||||
_ => Ordering::Greater,
|
||||
}
|
||||
},
|
||||
GameState::Playing { name, .. } => {
|
||||
match other {
|
||||
GameState::Playing { name: _name, .. } => name.cmp(_name),
|
||||
_ => Ordering::Less,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Visualiser game option
|
||||
|
@ -120,6 +175,7 @@ pub async fn get_games() -> Result<Vec<GameOption>, String> {
|
|||
Ok(games)
|
||||
}
|
||||
|
||||
use crate::planetwars::FinishedState;
|
||||
|
||||
use mozaic::modules::game;
|
||||
use mozaic::util::request::Connect;
|
||||
|
@ -130,34 +186,33 @@ pub async fn get_states(game_ids: &Vec<(String, u64)>, manager: &game::Manager)
|
|||
|
||||
for (gs, name) in gss {
|
||||
if let Some(state) = gs {
|
||||
let mut players: Vec<String> = state.iter().map(|conn| match conn {
|
||||
Connect::Waiting(_, key) => format!("Waiting {}", key),
|
||||
_ => String::from("Some connected player")
|
||||
}).collect();
|
||||
|
||||
players.sort();
|
||||
|
||||
match state {
|
||||
Ok(conns) => {
|
||||
let players: Vec<PlayerStatus> = conns.iter().cloned().map(|x| x.into()).collect();
|
||||
let connected = players.iter().filter(|x| x.connected).count();
|
||||
states.push(
|
||||
GameState {
|
||||
name,
|
||||
turns: None,
|
||||
players: players,
|
||||
finished: false,
|
||||
}
|
||||
)
|
||||
} else {
|
||||
GameState::Playing { name: name, total: players.len(), players, connected, map: String::new(), }
|
||||
);
|
||||
},
|
||||
Err(value) => {
|
||||
let state: FinishedState = serde_json::from_value(value).expect("Shit failed");
|
||||
states.push(
|
||||
GameState {
|
||||
name,
|
||||
turns: None,
|
||||
players: Vec::new(),
|
||||
finished: true,
|
||||
GameState::Finished {
|
||||
map: String::new(),
|
||||
players: state.players.iter().map(|(id, name)| (name.clone(), state.winners.contains(&id))).collect(),
|
||||
name: state.name,
|
||||
turns: state.turns,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
states.sort_by_key(|a| a.name.clone());
|
||||
states.sort();
|
||||
println!(
|
||||
"{}", serde_json::to_string_pretty(&states).unwrap(),
|
||||
);
|
||||
|
||||
Ok(states)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ body {
|
|||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: start;
|
||||
background: black;
|
||||
color: #ff7f00;
|
||||
line-height: 1.5rem;
|
||||
|
@ -66,11 +66,9 @@ body {
|
|||
.info a {
|
||||
/* color: inherit; */
|
||||
/* filter: saturate(100%); */
|
||||
|
||||
/* filter: invert(75%); */
|
||||
color: #ff7f00;
|
||||
filter: brightness(0.5);
|
||||
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -88,36 +86,30 @@ body {
|
|||
color: #ff7f00;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info div p {
|
||||
/* color: #ff7f00; */
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
@keyframes heartbeat
|
||||
{
|
||||
0%
|
||||
{
|
||||
transform: rotate(-45deg) scale( .75 );
|
||||
@keyframes heartbeat {
|
||||
0% {
|
||||
transform: rotate(-45deg) scale( .75);
|
||||
}
|
||||
20%
|
||||
{
|
||||
transform: rotate(-45deg) scale( 1 );
|
||||
20% {
|
||||
transform: rotate(-45deg) scale( 1);
|
||||
}
|
||||
40%
|
||||
{
|
||||
transform: rotate(-45deg) scale( .75 );
|
||||
40% {
|
||||
transform: rotate(-45deg) scale( .75);
|
||||
}
|
||||
60%
|
||||
{
|
||||
transform: rotate(-45deg) scale( 1 );
|
||||
60% {
|
||||
transform: rotate(-45deg) scale( 1);
|
||||
}
|
||||
80%
|
||||
{
|
||||
transform: rotate(-45deg) scale( .75 );
|
||||
80% {
|
||||
transform: rotate(-45deg) scale( .75);
|
||||
}
|
||||
100%
|
||||
{
|
||||
transform: rotate(-45deg) scale( .75 );
|
||||
100% {
|
||||
transform: rotate(-45deg) scale( .75);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,9 +122,7 @@ body {
|
|||
top: 0;
|
||||
transform: rotate(-45deg);
|
||||
width: 20px;
|
||||
|
||||
filter: none;
|
||||
|
||||
animation: heartbeat 2s infinite;
|
||||
}
|
||||
|
||||
|
|
76
backend/static/style/collapsable.css
Normal file
76
backend/static/style/collapsable.css
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
CSS for the main interaction
|
||||
*/
|
||||
|
||||
.accordion>input[type="checkbox"] {
|
||||
position: absolute;
|
||||
left: -100vw;
|
||||
}
|
||||
|
||||
.accordion .content {
|
||||
overflow-y: hidden;
|
||||
height: 0;
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.accordion>input[type="checkbox"]:checked~.content {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.accordion label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Styling
|
||||
*/
|
||||
|
||||
.accordion {
|
||||
margin: 1em;
|
||||
width: 90%;
|
||||
min-width: 250px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.accordion .content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.accordion>input[type="checkbox"]:checked~.content {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.accordion .handle {
|
||||
margin: 0;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
.accordion label {
|
||||
color: #ff7f00;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
padding: 15px;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.accordion label:hover,
|
||||
.accordion label:focus {
|
||||
filter: brightness(0.7);
|
||||
}
|
||||
|
||||
.accordion .handle label:before {
|
||||
font-family: 'fontawesome';
|
||||
content: "\f054";
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
font-size: .58em;
|
||||
line-height: 1.556em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.accordion>input[type="checkbox"]:checked~.handle label:before {
|
||||
content: "\f078";
|
||||
}
|
|
@ -15,8 +15,9 @@
|
|||
}
|
||||
|
||||
.grid {
|
||||
width: 120vh;
|
||||
height: 95vh;
|
||||
width: 85vh;
|
||||
height: 85vh;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
|
52
backend/static/style/state.css
Normal file
52
backend/static/style/state.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
.connected::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
margin: 0 -20px 0 0;
|
||||
font-family: 'fontawesome';
|
||||
content: "\f1eb";
|
||||
transform: translate(-30px, 0px);
|
||||
}
|
||||
|
||||
.waiting::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
margin: 0 -20px 0 0;
|
||||
font-family: 'fontawesome';
|
||||
content: "\f084";
|
||||
transform: rotate(180deg) translate(27px, -7px) scaleX(-1);
|
||||
}
|
||||
|
||||
.reconnecting::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
margin: 0 -20px 0 0;
|
||||
font-family: 'fontawesome';
|
||||
content: "\f0e7";
|
||||
transform: translate(-22px, 0px);
|
||||
}
|
||||
|
||||
.players {
|
||||
margin: 10px 10px 10px 50px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.winner::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
float: left;
|
||||
margin: 0 -20px 0 0;
|
||||
font-family: 'fontawesome';
|
||||
content: "\f091";
|
||||
transform: translate(-30px, 0px);
|
||||
}
|
|
@ -20,12 +20,12 @@ body {
|
|||
right: 25px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
background-color: #ff7f00;
|
||||
mask-image: url("refresh.svg");
|
||||
-webkit-mask-image: url("refresh.svg");
|
||||
animation: spin 2s linear infinite;
|
||||
animation-play-state: paused;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.refresh:hover {
|
||||
|
@ -34,12 +34,15 @@ body {
|
|||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {transform: rotate(1turn); }
|
||||
100% {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
.lobby_wrapper {
|
||||
flex-grow: 1;
|
||||
position: relative;;
|
||||
position: relative;
|
||||
;
|
||||
}
|
||||
|
||||
.lobby {
|
||||
|
@ -48,7 +51,7 @@ body {
|
|||
flex-wrap: wrap;
|
||||
align-self: flex-start;
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input_container {
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
{% extends "base" %}
|
||||
|
||||
{% block content %}
|
||||
<link rel="stylesheet" href="/style/collapsable.css">
|
||||
<link rel="stylesheet" href="/style/state.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
|
||||
<div class="main">
|
||||
<div class="lobby_wrapper">
|
||||
<div class="refresh" onclick="refresh_state()"></div>
|
||||
<div id="lobby" class="lobby">
|
||||
</div>
|
||||
<div id="lobby" class="lobby"></div>
|
||||
</div>
|
||||
<div class="creator">
|
||||
<h1>Start new game</h1>
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
{% for state in games %}
|
||||
<div class="game_state">
|
||||
<div class="info">
|
||||
<p>{{state.name}}</p>
|
||||
{% if state.finished %}<p>Finished</p> {% endif %}
|
||||
{% if state.turns %}<p>Turns: {{ state.turns }} </p> {% endif %}
|
||||
</div>
|
||||
<section class="accordion">
|
||||
<input type="checkbox" name="collapse" id="handle_{{loop.index}}">
|
||||
<h2 class="handle">
|
||||
<label for="handle_{{loop.index}}">
|
||||
<span>{{state.name}} ({{state.map}})</span>
|
||||
<span style="float: right">{% if state.type == "Finished" %}Done{% else %}{{ state.connected }}/{{state.total}}{% endif %}</span>
|
||||
</label>
|
||||
</h2>
|
||||
<div class="content">
|
||||
{% if state.type == "Playing" %}
|
||||
<div class="players">
|
||||
{% for player in state.players %}
|
||||
<p>{{ player }}</p>
|
||||
<p class="{% if player.waiting %}waiting {% endif %}{% if player.connected %}connected {% endif %}{% if player.reconnecting %}reconnecting {% endif %}">{{ player.value }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="players">
|
||||
{% for player in state.players %}
|
||||
<p class="{% if player[1] %}winner{% endif %}">{{ player[0] }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
{"nop":2,"name":"Dick But","map":"maps/hex.json","max_turns":2000}
|
||||
{"nop":2,"name":"Bexit","map":"maps/hex.json","max_turns":200}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import requests, json, subprocess, os
|
||||
|
||||
host = os.getenv("HOST") or "localhost:8000"
|
||||
|
||||
headers = {'content-type': 'application/json'}
|
||||
|
||||
r = requests.post(f"http://{host}/lobby", data=open('game_start.json').read(), headers=headers)
|
||||
data = r.json()
|
||||
try:
|
||||
r = requests.post(f"https://{host}/lobby", data=open('game_start.json').read(), headers=headers)
|
||||
data = r.json()
|
||||
except Exception:
|
||||
r = requests.post(f"http://{host}/lobby", data=open('game_start.json').read(), headers=headers)
|
||||
data = r.json()
|
||||
|
||||
processes = []
|
||||
|
||||
for player in data["players"]:
|
||||
|
|
|
@ -15,10 +15,9 @@ def execute(cmd):
|
|||
raise subprocess.CalledProcessError(return_code, cmd)
|
||||
|
||||
def connect(host, port, id):
|
||||
print(host, port)
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((host, port))
|
||||
s.sendall(f"{id.strip()}\n".encode("utf8"))
|
||||
s.sendall(f"{json.dumps(id)}\n".encode("utf8"))
|
||||
return s
|
||||
|
||||
def handle_input(it, socket):
|
||||
|
@ -33,11 +32,13 @@ def main():
|
|||
help='What host to connect to')
|
||||
parser.add_argument('--port', '-p', default=6666, type=int,
|
||||
help='What port to connect to')
|
||||
parser.add_argument('--name', '-n', default="Silvius",
|
||||
help='Who are you?')
|
||||
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)
|
||||
sock = connect(args.host, args.port, {"id": int(args.id), "name": args.name})
|
||||
f = sock.makefile("rw")
|
||||
|
||||
it = execute(args.arguments)
|
||||
|
@ -48,6 +49,7 @@ def main():
|
|||
line = f.readline()
|
||||
content = "Nothing"
|
||||
while line:
|
||||
print(line)
|
||||
content = json.loads(line)
|
||||
if content["type"] == "game_state":
|
||||
stdin.write(json.dumps(content["content"])+"\n")
|
||||
|
|
Loading…
Reference in a new issue