From 6f5d7338d074a76e0d274c6d3f0b4a862979dde5 Mon Sep 17 00:00:00 2001 From: Mashy Date: Sat, 26 Oct 2019 21:48:12 +1000 Subject: [PATCH 1/7] catch null idle case --- client/src/components/anims/wiggle.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/anims/wiggle.jsx b/client/src/components/anims/wiggle.jsx index e071450b..5886a47d 100644 --- a/client/src/components/anims/wiggle.jsx +++ b/client/src/components/anims/wiggle.jsx @@ -1,11 +1,12 @@ const anime = require('animejs').default; function wiggle(id, idle) { + if (!idle) return true; const duration = 300; const target = document.getElementById(id); const x = window.innerWidth * 0.01 * (Math.round(Math.random()) ? Math.random() : -Math.random()); const y = window.innerHeight * 0.01 * (Math.round(Math.random()) ? Math.random() : -Math.random()); - + const originalX = parseFloat(idle.animations[0].currentValue); const originalY = parseFloat(idle.animations[1].currentValue); // console.log(x, y); From d41ff0d4e8822f36b07517119ae6e7b923dd0aef Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Oct 2019 12:01:04 +1100 Subject: [PATCH 2/7] don't generate source maps --- client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index 6c6d56fe..790e7aed 100644 --- a/client/package.json +++ b/client/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "parcel watch index.html --out-dir /var/lib/mnml/public/current", "anims": "parcel watch animations.html --no-hmr --out-dir /var/lib/mnml/public/current", - "build": "parcel build index.html", + "build": "parcel build index.html --no-source-maps", "scss": "node-sass --watch assets/scss -o assets/styles", "lint": "eslint --fix --ext .jsx src/", "test": "echo \"Error: no test specified\" && exit 1" From 4593a5f2033e539fe76504810aad7cea42a44f12 Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Oct 2019 13:20:14 +1100 Subject: [PATCH 3/7] offer draws --- CHANGELOG.md | 18 +++++++ client/assets/styles/controls.less | 10 +++- client/src/components/game.ctrl.btns.jsx | 2 +- client/src/components/game.ctrl.btns.top.jsx | 23 +++++++-- client/src/components/game.footer.jsx | 4 +- client/src/socket.jsx | 6 +++ server/src/game.rs | 52 +++++++++++++++++--- server/src/instance.rs | 44 +++++------------ server/src/player.rs | 3 ++ server/src/rpc.rs | 6 ++- 10 files changed, 121 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 807de0fb..009c5d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +## [1.6.5] - 2019-10-27 +# Added +- Offering of draws + - Neither player receives a point if they agree to a draw + - Bots automatically agree to draws + +## [1.6.5] - 2019-10-25 +# Fixed +- Stripe being blocked no longer causes unrecoverable error +- Automatic ready up is now throttled after abandons +- Player width styling + +# Changed +- Improved wiggle animation +- Intercept is now considered defensive by bots +- Password restrictions relaxed + ## [1.6.4] - 2019-10-24 ### Changed - Animations processing on client side reduced. diff --git a/client/assets/styles/controls.less b/client/assets/styles/controls.less index 2ff3ee04..54bcb375 100644 --- a/client/assets/styles/controls.less +++ b/client/assets/styles/controls.less @@ -144,7 +144,15 @@ aside { &:active, &.confirming { background: @red; color: black; - border: 2px solid black; + border: 2px solid @red; + } +} + +.draw:not([disabled]) { + &:active, &.confirming { + background: @gray-hover; + color: black; + border: 2px solid @gray-hover; } } diff --git a/client/src/components/game.ctrl.btns.jsx b/client/src/components/game.ctrl.btns.jsx index 671e9d62..3918b5ca 100644 --- a/client/src/components/game.ctrl.btns.jsx +++ b/client/src/components/game.ctrl.btns.jsx @@ -75,7 +75,7 @@ function GameCtrlBtns(args) { } = args; if (!game) return false; - const finished = game.phase === 'Finish'; + const finished = game.phase === 'Finished'; function quitClick() { getInstanceState(); diff --git a/client/src/components/game.ctrl.btns.top.jsx b/client/src/components/game.ctrl.btns.top.jsx index 4ab0d543..4b81670c 100644 --- a/client/src/components/game.ctrl.btns.top.jsx +++ b/client/src/components/game.ctrl.btns.top.jsx @@ -14,10 +14,15 @@ const addState = connect( return ws.sendInstanceAbandon(game.instance); } + function sendDraw() { + return ws.sendGameOfferDraw(game.id); + } + return { game, sendAbandon, + sendDraw, }; }, function receiveDispatch(dispatch) { @@ -37,10 +42,11 @@ function GameCtrlTopBtns(args) { leave, sendAbandon, + sendDraw, } = args; const finished = game && game.phase === 'Finished'; - const { abandonState } = this.state; + const { abandonState, drawState } = this.state; const abandonStateTrue = e => { e.stopPropagation(); @@ -48,16 +54,27 @@ function GameCtrlTopBtns(args) { setTimeout(() => this.setState({ abandonState: false }), 2000); }; + const drawStateTrue = e => { + e.stopPropagation(); + this.setState({ drawState: true }); + setTimeout(() => this.setState({ drawState: false }), 2000); + }; + const abandonClasses = `abandon ${abandonState ? 'confirming' : ''}`; const abandonText = abandonState ? 'Confirm' : 'Abandon'; const abandonAction = abandonState ? sendAbandon : abandonStateTrue; const abandonBtn = ; - const leaveBtn = ; + + const drawClasses = `draw ${drawState ? 'confirming' : ''}`; + const drawText = drawState ? 'Draw' : 'Offer'; + const drawAction = drawState ? sendDraw : drawStateTrue; + const drawBtn = ; return (
- {finished ? leaveBtn : abandonBtn} + {abandonBtn} + {drawBtn}
); } diff --git a/client/src/components/game.footer.jsx b/client/src/components/game.footer.jsx index 101318a4..4a1589b0 100644 --- a/client/src/components/game.footer.jsx +++ b/client/src/components/game.footer.jsx @@ -82,7 +82,7 @@ function GameFooter(props) { const now = Date.now(); const end = Date.parse(game.phase_end); const timerPct = ((now - zero) / (end - zero) * 100); - const displayPct = game.phase === 'Finish' || !game.phase_end + const displayPct = game.phase === 'Finished' || !game.phase_end ? 0 : Math.min(timerPct, 100); @@ -108,7 +108,7 @@ function GameFooter(props) { return (
{timer} - {game.phase === 'Finish' && quitBtn } + {game.phase === 'Finished' && quitBtn } {game.phase === 'Skill' && readyBtn }
); diff --git a/client/src/socket.jsx b/client/src/socket.jsx index 517ee04f..85d81154 100644 --- a/client/src/socket.jsx +++ b/client/src/socket.jsx @@ -123,6 +123,11 @@ function createSocket(events) { events.setActiveSkill(null); } + function sendGameOfferDraw(gameId) { + send(['GameOfferDraw', { game_id: gameId }]); + events.setActiveSkill(null); + } + function sendGameTarget(gameId, constructId, skillId) { send(['GameTarget', { game_id: gameId, construct_id: constructId, skill_id: skillId }]); events.setActiveSkill(null); @@ -362,6 +367,7 @@ function createSocket(events) { sendGameReady, sendGameSkill, sendGameSkillClear, + sendGameOfferDraw, sendGameTarget, sendInstanceAbandon, diff --git a/server/src/game.rs b/server/src/game.rs index 0421ab65..12a175b4 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -27,7 +27,7 @@ pub enum Phase { Start, Skill, Resolve, - Finish, + Finished, } #[derive(Debug,Clone,Serialize,Deserialize)] @@ -325,6 +325,30 @@ impl Game { return Ok(self); } + fn offer_draw(mut self, player_id: Uuid) -> Result { + if self.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")); + } + + { + let player = self.player_by_id(player_id)?; + player.draw_offered = true; + } + + // bots automatically accept draws + for player in self.players.iter_mut() { + if player.bot { + player.draw_offered = true; + } + } + + if self.players.iter().all(|p| p.draw_offered) { + return Ok(self.finish()); + } + + return Ok(self); + } + fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { self.player_by_id(player_id)?; if self.phase != Phase::Skill { @@ -564,15 +588,18 @@ impl Game { // } pub fn finished(&self) -> bool { - self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) + self.phase == Phase::Finished || self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) } pub fn winner(&self) -> Option<&Player> { - self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())) + match self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) { + true => self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())), + false => None, + } } fn finish(mut self) -> Game { - self.phase = Phase::Finish; + self.phase = Phase::Finished; // self.log.push(format!("Game finished.")); // { @@ -594,7 +621,7 @@ impl Game { } pub fn upkeep(mut self) -> Game { - if self.phase == Phase::Finish { + if self.phase == Phase::Finished { return self; } @@ -903,6 +930,15 @@ pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, constr Ok(game) } +pub fn game_offer_draw(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { + let game = game_get(tx, game_id)? + .offer_draw(account.id)?; + + game_update(tx, &game)?; + + Ok(game) +} + pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { let mut game = game_get(tx, game_id)?; @@ -1051,7 +1087,7 @@ mod tests { game = game.resolve_phase_start(); - assert!([Phase::Skill, Phase::Finish].contains(&game.phase)); + assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); return; } @@ -1117,7 +1153,7 @@ mod tests { game = game.resolve_phase_start(); assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned()); - assert!(game.phase == Phase::Finish); + assert!(game.phase == Phase::Finished); } #[test] @@ -1423,7 +1459,7 @@ mod tests { assert!(game.skill_phase_finished()); game = game.resolve_phase_start(); - assert!([Phase::Skill, Phase::Finish].contains(&game.phase)); + assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); // kill a construct game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value()); diff --git a/server/src/instance.rs b/server/src/instance.rs index 8c92a821..eec93a0d 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -317,7 +317,13 @@ impl Instance { self.phase_start = Utc::now(); self.phase_end = self.time_control.vbox_phase_end(); + let bits = match self.rounds.len() > 0 { + true => 12 + 6 * self.rounds.len(), + false => 0, + }; + self.players.iter_mut().for_each(|p| { + p.vbox.balance_add(bits.into()); p.set_ready(false); p.vbox.fill(); }); @@ -412,38 +418,14 @@ impl Instance { } // if you don't win, you lose - // ties can happen if both players forfeit - // in this case we just finish the game and - // dock them 10k mmr - let winner_id = match game.winner() { - Some(w) => w.id, - None => return Ok(self.finish()), - }; - - let bits = 12 + 6 * self.rounds.len(); - - { - let loser = self.players.iter() - .find(|p| p.id != winner_id) - .map(|p| p.score) - .unwrap(); - + // ties can happen if both players agree to a draw + // or ticks fire and knock everybody out + if let Some(winner) = game.winner() { let winner = self.players.iter_mut() - .find(|p| p.id == winner_id) + .find(|p| p.id == winner.id) .unwrap(); - - winner.score = winner.score.add_win(&loser); - winner.vbox.balance_add(bits.into()); - } - - { - let loser = self.players.iter_mut() - .find(|p| p.id != winner_id) - .unwrap(); - - loser.score = loser.score.add_loss(); - loser.vbox.balance_add(bits.into()); - } + winner.score = winner.score.add_win(&Score::Zero); + }; if self.all_games_finished() { self.next_round(); @@ -799,7 +781,7 @@ pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result Ok(RpcMessage::GameState(game_ready(&mut tx, account, id)?)), + RpcRequest::GameOfferDraw { game_id } => + Ok(RpcMessage::GameState(game_offer_draw(&mut tx, account, game_id)?)), + RpcRequest::InstancePractice {} => Ok(RpcMessage::InstanceState(instance_practice(&mut tx, account)?)), From 6b6726f3a6d76fd2c5db66707c10b9381a33269d Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Oct 2019 13:20:58 +1100 Subject: [PATCH 4/7] v1.6.6 --- VERSION | 2 +- acp/package.json | 2 +- client/package.json | 2 +- ops/package.json | 2 +- server/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 49ebdd60..83d1a5eb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.5 \ No newline at end of file +1.6.6 \ No newline at end of file diff --git a/acp/package.json b/acp/package.json index 2591fabe..a1935bb7 100644 --- a/acp/package.json +++ b/acp/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "1.6.5", + "version": "1.6.6", "description": "", "main": "index.js", "scripts": { diff --git a/client/package.json b/client/package.json index 790e7aed..b6b1c359 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "1.6.5", + "version": "1.6.6", "description": "", "main": "index.js", "scripts": { diff --git a/ops/package.json b/ops/package.json index 40211410..ae05c7f0 100755 --- a/ops/package.json +++ b/ops/package.json @@ -1,6 +1,6 @@ { "name": "mnml-ops", - "version": "1.6.5", + "version": "1.6.6", "description": "", "main": "index.js", "scripts": { diff --git a/server/Cargo.toml b/server/Cargo.toml index 4df35fa1..c80430f6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mnml" -version = "1.6.5" +version = "1.6.6" authors = ["ntr "] [dependencies] From 343731e7e894f25c317bdc7b7e1a51a2a4a61f4b Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Oct 2019 14:12:38 +1100 Subject: [PATCH 5/7] draw fixes and client side showing --- client/src/components/game.ctrl.btns.top.jsx | 14 +++++++++++--- client/src/components/player.box.jsx | 9 +++++++-- server/src/instance.rs | 19 +------------------ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/client/src/components/game.ctrl.btns.top.jsx b/client/src/components/game.ctrl.btns.top.jsx index 4b81670c..8e09718c 100644 --- a/client/src/components/game.ctrl.btns.top.jsx +++ b/client/src/components/game.ctrl.btns.top.jsx @@ -8,6 +8,7 @@ const addState = connect( const { ws, game, + account, } = state; function sendAbandon() { @@ -20,6 +21,7 @@ const addState = connect( return { game, + account, sendAbandon, sendDraw, @@ -39,6 +41,7 @@ const addState = connect( function GameCtrlTopBtns(args) { const { game, + account, leave, sendAbandon, @@ -48,6 +51,9 @@ function GameCtrlTopBtns(args) { const finished = game && game.phase === 'Finished'; const { abandonState, drawState } = this.state; + const player = game.players.find(p => p.id === account.id); + const drawOffered = player && player.draw_offered; + const abandonStateTrue = e => { e.stopPropagation(); this.setState({ abandonState: true }); @@ -66,10 +72,12 @@ function GameCtrlTopBtns(args) { const abandonBtn = ; - const drawClasses = `draw ${drawState ? 'confirming' : ''}`; - const drawText = drawState ? 'Draw' : 'Offer'; + const drawClasses = `draw ${drawState || drawOffered ? 'confirming' : ''}`; + const drawText = drawOffered + ? 'Offered' + : drawState ? 'Draw' : 'Offer'; const drawAction = drawState ? sendDraw : drawStateTrue; - const drawBtn = ; + const drawBtn = ; return (
diff --git a/client/src/components/player.box.jsx b/client/src/components/player.box.jsx index 2790d69f..9d55606e 100644 --- a/client/src/components/player.box.jsx +++ b/client/src/components/player.box.jsx @@ -68,6 +68,11 @@ function Scoreboard(args) { }; const winner = player.score === 'Win'; + const chatText = player.draw_offered + ? 'draw?' + : chat || '\u00A0'; + + console.log(chatText); if (!isPlayer) { const nameClass = `name ${player.img ? 'subscriber' : ''}`; @@ -77,7 +82,7 @@ function Scoreboard(args) {
{scoreText()}
{player.name}
-
{chat || '\u00A0'}
+
{chatText}
); } @@ -87,7 +92,7 @@ function Scoreboard(args) { return (
-
{chat || '\u00A0'}
+
{chatText}
{scoreText()}
{player.name}
diff --git a/server/src/instance.rs b/server/src/instance.rs index eec93a0d..07498300 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -335,24 +335,7 @@ impl Instance { } fn finish_condition(&mut self) -> bool { - // tennis - for player in self.players.iter() { - if player.score == Score::Win { - self.winner = Some(player.id); - return true; - } - } - // Game defaults to lose otherwise - if self.rounds.len() < 4 { - return false; - } - - // both players afk - if self.players.iter().all(|p| p.score == Score::Zero) { - return true; - } - - return false; + self.players.iter().any(|p| p.score == Score::Win) } pub fn finish(&mut self) -> &mut Instance { From d5764f0067d2f0b94485423cf9361f2d76323446 Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Oct 2019 14:18:20 +1100 Subject: [PATCH 6/7] draw text change --- client/src/components/player.box.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/components/player.box.jsx b/client/src/components/player.box.jsx index 9d55606e..6cace8a4 100644 --- a/client/src/components/player.box.jsx +++ b/client/src/components/player.box.jsx @@ -68,11 +68,11 @@ function Scoreboard(args) { }; const winner = player.score === 'Win'; - const chatText = player.draw_offered - ? 'draw?' - : chat || '\u00A0'; - - console.log(chatText); + const chatText = chat + ? chat + : player.draw_offered + ? 'draw' + : '\u00A0'; if (!isPlayer) { const nameClass = `name ${player.img ? 'subscriber' : ''}`; From 45aad71939b2227e4e3a58a9457f1a825ff9b9cc Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Oct 2019 14:57:27 +1100 Subject: [PATCH 7/7] changelog --- CHANGELOG.md | 1 - server/src/instance.rs | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009c5d41..01b06b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). - ## [1.6.5] - 2019-10-27 # Added - Offering of draws diff --git a/server/src/instance.rs b/server/src/instance.rs index 07498300..2e65b9d6 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -340,6 +340,13 @@ impl Instance { pub fn finish(&mut self) -> &mut Instance { self.phase = InstancePhase::Finished; + + for player in self.players.iter() { + if player.score == Score::Win { + self.winner = Some(player.id); + } + } + self }