Show players in lobby

This commit is contained in:
ajuvercr 2020-04-01 20:49:50 +02:00
parent c64535675f
commit 86ffa9726c
14 changed files with 325 additions and 126 deletions

View file

@ -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()
}

View file

@ -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)>,
}

View file

@ -116,15 +116,21 @@ 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 {
let players: Vec<u64> = conns.iter().map(|conn| match conn {
Connect::Waiting(_, key) => *key,
_ => 0,
}).collect();
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 {
Err(String::from("Fuck the world"))
Ok(Json(GameRes { players }))
},
Some(Err(v)) => {
Err(serde_json::to_string(&v).unwrap())
},
None => {
Err(String::from("Fuck the world"))
}
}
}

View file

@ -9,12 +9,67 @@ pub struct Map {
url: String,
}
#[derive(Serialize)]
pub struct GameState {
name: String,
finished: bool,
turns: Option<u64>,
players: Vec<String>,
#[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,
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();
states.push(
GameState {
name,
turns: None,
players: players,
finished: false,
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::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::Finished {
map: String::new(),
players: state.players.iter().map(|(id, name)| (name.clone(), state.winners.contains(&id))).collect(),
name: state.name,
turns: state.turns,
}
);
}
)
} else {
states.push(
GameState {
name,
turns: None,
players: Vec::new(),
finished: true,
}
)
}
}
}
states.sort_by_key(|a| a.name.clone());
states.sort();
println!(
"{}", serde_json::to_string_pretty(&states).unwrap(),
);
Ok(states)
}

View file

@ -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,37 +86,31 @@ body {
color: #ff7f00;
margin-bottom: 20px;
}
.info div p {
/* color: #ff7f00; */
margin: 5px 0;
}
@keyframes heartbeat
{
0%
{
transform: rotate(-45deg) scale( .75 );
}
20%
{
transform: rotate(-45deg) scale( 1 );
}
40%
{
transform: rotate(-45deg) scale( .75 );
}
60%
{
transform: rotate(-45deg) scale( 1 );
}
80%
{
transform: rotate(-45deg) scale( .75 );
}
100%
{
transform: rotate(-45deg) scale( .75 );
}
@keyframes heartbeat {
0% {
transform: rotate(-45deg) scale( .75);
}
20% {
transform: rotate(-45deg) scale( 1);
}
40% {
transform: rotate(-45deg) scale( .75);
}
60% {
transform: rotate(-45deg) scale( 1);
}
80% {
transform: rotate(-45deg) scale( .75);
}
100% {
transform: rotate(-45deg) scale( .75);
}
}
.heart {
@ -130,9 +122,7 @@ body {
top: 0;
transform: rotate(-45deg);
width: 20px;
filter: none;
animation: heartbeat 2s infinite;
}
@ -154,4 +144,4 @@ body {
.heart:after {
left: 10px;
top: 0;
}
}

View 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";
}

View file

@ -15,8 +15,9 @@
}
.grid {
width: 120vh;
height: 95vh;
width: 85vh;
height: 85vh;
margin: auto;
display: flex;
flex-wrap: wrap;
}

View 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);
}

View file

@ -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 {
@ -142,4 +145,4 @@ svg {
margin: 20px auto;
justify-content: space-between;
padding: 20px;
}
}

View file

@ -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>

View file

@ -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 %}
<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 class="{% if player.waiting %}waiting {% endif %}{% if player.connected %}connected {% endif %}{% if player.reconnecting %}reconnecting {% endif %}">{{ player.value }}</p>
{% endfor %}
</div>
{% else %}
<div class="players">
{% for player in state.players %}
<p class="{% if player[1] %}winner{% endif %}">{{ player[0] }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="players">
{% for player in state.players %}
<p>{{ player }}</p>
{% endfor %}
</div>
</div>
</section>
{% endfor %}

View file

@ -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}

View file

@ -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"]:

View file

@ -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")