clientside rounds changes

This commit is contained in:
ntr 2019-06-06 00:03:27 +10:00
parent 1f5fba80f1
commit 6e513e3da7
9 changed files with 95 additions and 159 deletions

View File

@ -446,7 +446,7 @@ header {
flex: 1 1 50%; flex: 1 1 50%;
} }
.create-form input { .create-form .login-input {
width: 50%; width: 50%;
} }

View File

@ -23,8 +23,8 @@ document.fonts.load('16pt "Jura"').then(() => {
setupKeys(store); setupKeys(store);
const ws = createSocket(events); const ws = createSocket(events);
store.dispatch(actions.setWs(ws));
ws.connect(); ws.connect();
events.setWs(ws);
const App = () => ( const App = () => (
<Provider store={store}> <Provider store={store}>

View File

@ -65,7 +65,7 @@ function Instance(args) {
<tr key={i} <tr key={i}
className={p.ready ? 'ready' : ''}> className={p.ready ? 'ready' : ''}>
<td>{p.name}</td> <td>{p.name}</td>
<td>{p.score.wins} / {p.score.losses}</td> <td>{p.wins} / {p.losses}</td>
<td>{pText}</td> <td>{pText}</td>
</tr> </tr>
); );

View File

@ -6,9 +6,9 @@ const addState = connect(
function receiveState(state) { function receiveState(state) {
const { ws, team } = state; const { ws, team } = state;
function sendInstanceNew(sConstructs, name, players) { function sendInstanceNew(sConstructs, name, pve) {
if (sConstructs.length) { if (sConstructs.length) {
return ws.sendInstanceNew(sConstructs, name, players); return ws.sendInstanceNew(sConstructs, name, pve);
} }
return false; return false;
} }
@ -24,19 +24,19 @@ class InstanceCreateForm extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { players: 2, name: '' }; this.state = { pve: false, name: '' };
const { sendInstanceNew } = props; const { sendInstanceNew } = props;
this.sendInstanceNew = sendInstanceNew.bind(this); this.sendInstanceNew = sendInstanceNew.bind(this);
this.nameInput = this.nameInput.bind(this); this.nameInput = this.nameInput.bind(this);
this.playersChange = this.playersChange.bind(this); this.pveChange = this.pveChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
} }
playersChange(event) { pveChange() {
this.setState({ players: Number(event.target.value) }); this.setState({ pve: !this.state.pve });
} }
nameInput(event) { nameInput(event) {
@ -45,8 +45,8 @@ class InstanceCreateForm extends Component {
handleSubmit(event) { handleSubmit(event) {
event.preventDefault(); event.preventDefault();
this.sendInstanceNew(this.props.team, this.state.name, this.state.players); this.sendInstanceNew(this.props.team, this.state.name, this.state.pve);
this.setState({ name: '', players: 2 }); this.setState({ name: '', pve: false });
} }
render() { render() {
@ -62,21 +62,17 @@ class InstanceCreateForm extends Component {
type="text" type="text"
disabled={disabled} disabled={disabled}
value={this.state.name} value={this.state.name}
placeholder="name" placeholder="game name"
onInput={this.nameInput} onInput={this.nameInput}
/> />
<label htmlFor="playerSelect">players</label> <label htmlFor="pveSelect">vs CPU</label>
<select id="playerSelect" <input id="pveSelect"
type="checkbox"
disabled={disabled} disabled={disabled}
value={this.state.players} checked={this.state.pve}
onChange={this.playersChange} onChange={this.pveChange}
> >
<option value={1}>pve</option> </input>
<option value={2}>2</option>
<option value={4}>4</option>
<option value={8}>8</option>
<option value={16}>16</option>
</select>
</form> </form>
<button <button
onClick={this.handleSubmit} onClick={this.handleSubmit}

View File

@ -56,7 +56,7 @@ function List(args) {
const instancePanels = instanceList.map(instance => { const instancePanels = instanceList.map(instance => {
const player = instance.players.find(p => p.id === account.id); const player = instance.players.find(p => p.id === account.id);
const scoreText = player const scoreText = player
? `${player.score.wins} : ${player.score.losses}` ? `${player.wins} : ${player.losses}`
: ''; : '';
function instanceClick() { function instanceClick() {

View File

@ -125,8 +125,8 @@ function createSocket(events) {
send({ method: 'instance_join', params: { instance_id: instanceId, construct_ids: constructs } }); send({ method: 'instance_join', params: { instance_id: instanceId, construct_ids: constructs } });
} }
function sendInstanceNew(constructs, name, players) { function sendInstanceNew(constructs, name, pve) {
send({ method: 'instance_new', params: { construct_ids: constructs, name, players } }); send({ method: 'instance_new', params: { construct_ids: constructs, name, pve } });
} }
function sendInstanceReady(instanceId) { function sendInstanceReady(instanceId) {
@ -284,6 +284,7 @@ function createSocket(events) {
if (account) { if (account) {
events.setAccount(account); events.setAccount(account);
sendAccountInstances(); sendAccountInstances();
sendInstanceList();
sendAccountConstructs(); sendAccountConstructs();
} }

View File

@ -38,18 +38,23 @@ enum Format {
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
struct Round { struct Round {
player_ids: Vec<Uuid>,
game_id: Option<Uuid>, game_id: Option<Uuid>,
finished: bool, finished: bool,
} }
impl Round {
fn new() -> Round {
Round { game_id: None, finished: false }
}
}
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Instance { pub struct Instance {
id: Uuid, id: Uuid,
pub name: String, pub name: String,
players: Vec<Player>, players: Vec<Player>,
rounds: Vec<Vec<Round>>, rounds: Vec<Round>,
open: bool, open: bool,
max_players: usize, max_players: usize,
@ -72,13 +77,13 @@ impl Instance {
phase: InstancePhase::Lobby, phase: InstancePhase::Lobby,
open: true, open: true,
max_players: 2, max_players: 2,
max_rounds: 16, max_rounds: 5,
name: String::new(), name: String::new(),
password: None, password: None,
phase_end: Utc::now(), phase_end: Utc::now(),
phase_start: Utc::now(), phase_start: Utc::now(),
format: Format::RoundRobin, format: Format::Standard,
} }
} }
@ -90,7 +95,7 @@ impl Instance {
phase: InstancePhase::InProgress, phase: InstancePhase::InProgress,
open: false, open: false,
max_players: 0, max_players: 0,
max_rounds: 1, max_rounds: 5,
name: "Global Matchmaking".to_string(), name: "Global Matchmaking".to_string(),
password: None, password: None,
phase_start: Utc::now(), phase_start: Utc::now(),
@ -108,7 +113,6 @@ impl Instance {
self.players self.players
.iter() .iter()
.filter(|p| !p.ready) .filter(|p| !p.ready)
.filter(|p| self.current_game_id(p.id).is_none())
.map(|p| p.id) .map(|p| p.id)
.collect::<Vec<Uuid>>() .collect::<Vec<Uuid>>()
} }
@ -131,15 +135,6 @@ impl Instance {
(self, new_games) (self, new_games)
} }
fn set_max_players(mut self, max: usize) -> Result<Instance, Error> {
if max > 16 || max % 2 != 0 {
return Err(err_msg("max players must be divisible by 2 and less than 16"));
}
self.max_players = max;
Ok(self)
}
fn set_name(mut self, name: String) -> Result<Instance, Error> { fn set_name(mut self, name: String) -> Result<Instance, Error> {
if name.len() == 0 { if name.len() == 0 {
return Err(err_msg("name must have a length")); return Err(err_msg("name must have a length"));
@ -156,15 +151,17 @@ impl Instance {
fn add_bots(mut self) -> Instance { fn add_bots(mut self) -> Instance {
self.open = false; self.open = false;
self.players = iter::repeat_with(|| { iter::repeat_with(|| {
let bot_id = Uuid::new_v4(); let bot_id = Uuid::new_v4();
let constructs = instance_mobs(bot_id); let constructs = instance_mobs(bot_id);
let mut p = Player::new(bot_id, &name(), constructs).set_bot(true); let mut p = Player::new(bot_id, &name(), constructs).set_bot(true);
p.set_ready(true); p.set_ready(true);
p p
}) })
.take(15) // .take(self.max_players - self.players.len())
.collect::<Vec<Player>>(); .take(1)
.for_each(|p| self.players.push(p));
self self
} }
@ -223,52 +220,47 @@ impl Instance {
// create a game object if both players are ready // create a game object if both players are ready
// this should only happen once // this should only happen once
let all_ready = self.round_ready_check(player_id); let all_ready = self.round_ready_check();
if !all_ready { if !all_ready {
return Ok(None); return Ok(None);
} }
let game = self.create_round_game(player_id); let game = self.create_round_game();
{ let current_round = self.rounds
let round_num = self.rounds.len() - 1; .last_mut()
let current_round = self.rounds[round_num] .expect("instance does not have any rounds");
.iter_mut()
.find(|g| g.player_ids.contains(&player_id))
.unwrap();
current_round.game_id = Some(game.id); current_round.game_id = Some(game.id);
}
return Ok(Some(game)); return Ok(Some(game));
} }
fn round_ready_check(&mut self, player_id: Uuid) -> bool { fn round_ready_check(&mut self) -> bool {
let current_round = self.current_round(player_id);
self.players self.players
.iter() .iter()
.filter(|p| current_round.player_ids.contains(&p.id))
.all(|p| p.ready) .all(|p| p.ready)
} }
// maybe just embed the games in the instance // maybe just embed the games in the instance
// but seems hella inefficient // but seems hella inefficient
fn create_round_game(&self, player_id: Uuid) -> Game { fn create_round_game(&mut self) -> Game {
let current_round = self.current_round(player_id); let current_round = self.rounds
.last_mut()
.expect("instance does not have any rounds");
let mut game = Game::new(); let mut game = Game::new();
current_round.game_id = Some(game.id);
game game
.set_player_num(2) .set_player_num(2)
.set_player_constructs(3) .set_player_constructs(3)
.set_instance(self.id); .set_instance(self.id);
// create the initiators player for player in self.players.clone().into_iter() {
for player in self.players
.clone()
.into_iter()
.filter(|p| current_round.player_ids.contains(&p.id)) {
game.player_add(player).unwrap(); game.player_add(player).unwrap();
} }
@ -303,7 +295,7 @@ impl Instance {
p.vbox.fill(); p.vbox.fill();
}); });
self.generate_rounds(); self.rounds.push(Round::new());
self.bot_round_actions(); self.bot_round_actions();
self self
@ -313,7 +305,9 @@ impl Instance {
match self.format { match self.format {
// bo5 standard // bo5 standard
// OR condition is for forfeitures // OR condition is for forfeitures
Format::Standard => self.players.iter().any(|p| p.score.wins > 2) || self.rounds.len() == 5, Format::Standard =>
self.players.iter().any(|p| p.wins as usize >= self.max_rounds / 2 + 1)
|| self.rounds.len() == self.max_rounds,
// everybody plays each other once // everybody plays each other once
Format::RoundRobin => self.rounds.len() == self.players.len() - 1, Format::RoundRobin => self.rounds.len() == self.players.len() - 1,
@ -353,54 +347,14 @@ impl Instance {
self self
} }
fn generate_rounds(&mut self) -> &mut Instance { fn current_game_id(&self) -> Option<Uuid> {
let round_num = self.rounds.len();
let mut matched_players = self.players
.iter()
.map(|p| p.id)
.collect::<Vec<Uuid>>();
let np = matched_players.len();
if round_num > 0 {
matched_players.rotate_right(round_num % np);
matched_players.swap(0,1);
}
// only set up for even player numbers atm
// no byes
let current_round = matched_players[0..(np / 2)]
.iter()
.enumerate()
.map(|(i, id)| Round {
player_ids: vec![*id, matched_players[np - (i + 1)]],
game_id: None,
finished: false,
})
.collect::<Vec<Round>>();
self.rounds.push(current_round);
self
}
fn current_round(&self, player_id: Uuid) -> &Round {
let round_num = self.rounds.len() - 1;
let current_round = self.rounds[round_num]
.iter()
.find(|g| g.player_ids.contains(&player_id))
.unwrap();
current_round
}
fn current_game_id(&self, player_id: Uuid) -> Option<Uuid> {
if self.phase == InstancePhase::Lobby { if self.phase == InstancePhase::Lobby {
return None; return None;
} }
let current_round = self.current_round(player_id); let current_round = self.rounds
.last()
.expect("instance does not have any rounds");
if current_round.finished || current_round.game_id.is_none() { if current_round.finished || current_round.game_id.is_none() {
return None; return None;
@ -410,13 +364,17 @@ impl Instance {
} }
fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
let round_num = self.rounds.len() - 1; {
self.rounds[round_num] let current_round = self.rounds
.iter_mut() .last_mut()
.filter(|r| r.game_id.is_some()) .expect("instance does not have any rounds");
.find(|r| r.game_id.unwrap() == game.id)
.ok_or(err_msg("could not find matchup in current round"))? if current_round.game_id.unwrap() != game.id {
.finished = true; return Err(err_msg("instance does not have a round for this game"));
}
current_round.finished = true;
}
// if you don't win, you lose // if you don't win, you lose
// ties can happen if both players forfeit // ties can happen if both players forfeit
@ -446,7 +404,7 @@ impl Instance {
fn all_games_finished(&self) -> bool { fn all_games_finished(&self) -> bool {
match self.rounds.last() { match self.rounds.last() {
Some(r) => r.iter().all(|g| g.finished), Some(r) => r.finished,
None => true, None => true,
} }
} }
@ -460,10 +418,15 @@ impl Instance {
} }
pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> { pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> {
if self.players.iter().find(|p| p.id == account).is_none() {
return Err(err_msg("player not in this instance"));
}
if self.phase == InstancePhase::Lobby { if self.phase == InstancePhase::Lobby {
return Err(err_msg("game not yet started")); return Err(err_msg("game not yet started"));
} }
if self.current_game_id(account).is_some() {
if self.current_game_id().is_some() {
return Err(err_msg("you cannot perform vbox actions while in a game")); return Err(err_msg("you cannot perform vbox actions while in a game"));
} }
@ -653,20 +616,13 @@ pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Erro
} }
pub fn instance_new(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> { pub fn instance_new(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let mut instance = match params.players { let mut instance = match params.pve {
1 => Instance::new() true => Instance::new()
.set_max_players(16)?
.set_name(params.name)? .set_name(params.name)?
.add_bots(), .add_bots(),
2 => Instance::new() false => Instance::new()
.set_max_players(params.players)?
.set_name(params.name)? .set_name(params.name)?
.set_format(Format::Standard),
_ => Instance::new()
.set_max_players(params.players)?
.set_name(params.name)?,
}; };
instance = instance_create(tx, instance)?; instance = instance_create(tx, instance)?;
@ -705,10 +661,10 @@ pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account
instance_update(tx, instance) instance_update(tx, instance)
} }
pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result<RpcResult, Error> { pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, _account: &Account) -> Result<RpcResult, Error> {
let instance = instance_get(tx, params.instance_id)?; let instance = instance_get(tx, params.instance_id)?;
if let Some(game_id) = instance.current_game_id(account.id) { if let Some(game_id) = instance.current_game_id() {
let game = game_get(tx, game_id)?; let game = game_get(tx, game_id)?;
// return the game until it's finished // return the game until it's finished
@ -736,24 +692,19 @@ mod tests {
#[test] #[test]
fn instance_pve_test() { fn instance_pve_test() {
let mut instance = Instance::new() let mut instance = Instance::new().add_bots();
.set_max_players(16).expect("unable to set max players")
.set_format(Format::RoundRobin)
.add_bots();
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
let player_id = player.id;
instance.add_player(player).expect("could not add player"); instance.add_player(player).expect("could not add player");
assert_eq!(instance.phase, InstancePhase::Lobby); assert_eq!(instance.phase, InstancePhase::Lobby);
instance.player_ready(player_id).unwrap(); instance.player_ready(player_account).unwrap();
assert_eq!(instance.phase, InstancePhase::Finished); assert_eq!(instance.phase, InstancePhase::Finished);
assert_eq!(instance.rounds[0].len(), 8); assert!(instance.players.iter().any(|p| p.wins as usize == instance.max_rounds / 2 + 1));
assert_eq!(instance.rounds.len(), 15);
} }
#[test] #[test]
@ -766,9 +717,7 @@ mod tests {
#[test] #[test]
fn instance_start_test() { fn instance_start_test() {
let mut instance = Instance::new() let mut instance = Instance::new();
.set_max_players(2)
.expect("could not create instance");
assert_eq!(instance.max_players, 2); assert_eq!(instance.max_players, 2);
@ -807,9 +756,7 @@ mod tests {
#[test] #[test]
fn instance_upkeep_test() { fn instance_upkeep_test() {
let mut instance = Instance::new() let mut instance = Instance::new();
.set_max_players(2).expect("could not create instance")
.set_format(Format::Standard);
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);

View File

@ -14,22 +14,17 @@ use skill::{Effect};
const DISCARD_COST: u16 = 5; const DISCARD_COST: u16 = 5;
#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
pub struct Score {
pub wins: u8,
pub losses: u8,
}
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Player { pub struct Player {
pub id: Uuid, pub id: Uuid,
pub name: String, pub name: String,
pub vbox: Vbox, pub vbox: Vbox,
pub score: Score,
pub constructs: Vec<Construct>, pub constructs: Vec<Construct>,
pub bot: bool, pub bot: bool,
pub ready: bool, pub ready: bool,
pub warnings: u8, pub warnings: u8,
pub wins: u8,
pub losses: u8,
} }
impl Player { impl Player {
@ -38,7 +33,8 @@ impl Player {
id: account, id: account,
name: name.clone(), name: name.clone(),
vbox: Vbox::new(), vbox: Vbox::new(),
score: Score { wins: 0, losses: 0 }, wins: 0,
losses: 0,
constructs, constructs,
bot: false, bot: false,
ready: false, ready: false,
@ -69,13 +65,13 @@ impl Player {
} }
pub fn add_win(&mut self) -> &mut Player { pub fn add_win(&mut self) -> &mut Player {
self.score.wins += 1; self.wins += 1;
self.vbox.balance_add(12); self.vbox.balance_add(12);
self self
} }
pub fn add_loss(&mut self) -> &mut Player { pub fn add_loss(&mut self) -> &mut Player {
self.score.losses += 1; self.losses += 1;
self.vbox.balance_add(9); self.vbox.balance_add(9);
self self
} }

View File

@ -17,7 +17,6 @@ use game::{Game, game_state, game_skill, game_ready};
use account::{Account, account_create, account_login, account_from_token, account_constructs, account_instances}; use account::{Account, account_create, account_login, account_from_token, account_constructs, account_instances};
use skill::{Skill}; use skill::{Skill};
use spec::{Spec}; use spec::{Spec};
use player::{Score};
use instance::{Instance, instance_state, instance_list, instance_new, instance_ready, instance_join}; use instance::{Instance, instance_state, instance_list, instance_new, instance_ready, instance_join};
use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip}; use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip};
use item::{Item, ItemInfoCtr, item_info}; use item::{Item, ItemInfoCtr, item_info};
@ -389,9 +388,6 @@ pub enum RpcResult {
ConstructList(Vec<Construct>), ConstructList(Vec<Construct>),
GameState(Game), GameState(Game),
ItemInfo(ItemInfoCtr), ItemInfo(ItemInfoCtr),
InstanceScores(Vec<(String, Score)>),
// ZoneState(Zone),
// ZoneClose(()),
InstanceList(Vec<Instance>), InstanceList(Vec<Instance>),
InstanceState(Instance), InstanceState(Instance),
@ -528,7 +524,7 @@ struct InstanceLobbyMsg {
pub struct InstanceLobbyParams { pub struct InstanceLobbyParams {
pub construct_ids: Vec<Uuid>, pub construct_ids: Vec<Uuid>,
pub name: String, pub name: String,
pub players: usize, pub pve: bool,
pub password: Option<String>, pub password: Option<String>,
} }