From 78c3b075b8bcf00ec9e68b4d657080e6351c0606 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Jan 2019 00:02:32 -0500 Subject: [PATCH] [add] client connections, lobby --- src/components/play/Board.js | 113 ++++++++++++------------ src/components/play/Gui.js | 4 +- src/components/play/Lobby.js | 41 +++++++++ src/components/play/Turn.js | 39 ++++++++ src/components/play/_base.js | 6 +- src/components/play/index.js | 122 ++++++++++++++++++++++---- src/components/play/rules/movement.js | 41 ++++++++- src/components/play/rules/phase.js | 18 ++++ src/components/play/rules/turn.js | 48 ---------- src/scss/play/lobby.scss | 1 + 10 files changed, 306 insertions(+), 127 deletions(-) create mode 100644 src/components/play/Lobby.js create mode 100644 src/components/play/Turn.js create mode 100644 src/components/play/rules/phase.js delete mode 100644 src/components/play/rules/turn.js create mode 100644 src/scss/play/lobby.scss diff --git a/src/components/play/Board.js b/src/components/play/Board.js index d5a1fda..b81ae4c 100644 --- a/src/components/play/Board.js +++ b/src/components/play/Board.js @@ -4,7 +4,7 @@ import {observable} from "mobx"; import {observer, inject} from 'mobx-react'; import API from '../SpreadsheetData'; import Movement from './rules/movement'; -import {Phase} from './rules/turn'; +import Phase from './rules/phase'; import '../../scss/play/battleboard.scss'; @observer @@ -16,31 +16,57 @@ export default class Board extends Base { super(props); this.selectCard = this.selectCard.bind(this); + // Movement Rule this.movement = new Movement(); + // Create board this.spaces = Array(12).fill({ 'creatures': [this.empty_card("creatures")], 'battlegear': [this.empty_card("battlegear")], 'mirage': [this.empty_card("mirage")] }); - this.loadcards(); - } - - // TODO - loadcards() { - this.spaces[4].creatures[0].data = API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Maxxor", 'i')}}); - this.spaces[4].battlegear[0].data = API.cards.battlegear.findOne({'gsx$name': {'$regex': new RegExp("Maxxor's Torch", 'i')}}); - this.spaces[1].creatures[0].data = API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Staluk", 'i')}}); - this.spaces[1].battlegear[0].data = API.cards.battlegear.findOne({'gsx$name': {'$regex': new RegExp("Vlaric Shard", 'i')}}); - this.spaces[0].creatures[0].data = API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Vidav", 'i')}}); - this.spaces[2].creatures[0].data = API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Dractyl", 'i')}}); for (let i = 0; i < 6; i++) { this.spaces[i].creatures[0].controlled = true; this.spaces[i].battlegear[0].controlled = true; } - this.spaces[7].creatures[0].data = API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Chaor", 'i')}}); - this.spaces[7].battlegear[0].data = API.cards.battlegear.findOne({'gsx$name': {'$regex': new RegExp("Whepcrack", 'i')}}); + + setTimeout(() => this.loadgame(), 500); // TODO + } + + // todo remove / currently simulating + loadgame() { + if (this.props.player == 0 || this.props.player == 1) { + this.submitChange({event: "load", action: {space: 0, type: "creatures", + data: API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Vidav", 'i')}}) + }}); + this.submitChange({event: "load", action: {space: 1, type: "creatures", + data: API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Staluk", 'i')}}) + }}); + this.submitChange({event: "load", action: {space: 1, type: "battlegear", + data: API.cards.battlegear.findOne({'gsx$name': {'$regex': new RegExp("Vlaric Shard", 'i')}}) + }}); + this.submitChange({event: "load", action: {space: 2, type: "creatures", + data: API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Dractyl", 'i')}}) + }}); + this.submitChange({event: "load", action: {space: 4, type: "creatures", + data: API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Maxxor", 'i')}}) + }}); + this.submitChange({event: "load", action: {space: 4, type: "battlegear", + data: API.cards.battlegear.findOne({'gsx$name': {'$regex': new RegExp("Maxxor's Torch", 'i')}}) + }}); + + this.makeChange({event:"active", action: true}); + this.submitChange({event: "phase", action: "movement"}, "turn"); + } + else { + this.submitChange({event: "load", action: {space: 4, type: "creatures", + data: API.cards.creatures.findOne({'gsx$name': {'$regex': new RegExp("Chaor", 'i')}}) + }}); + this.submitChange({event: "load", action: {space: 4, type: "battlegear", + data: API.cards.battlegear.findOne({'gsx$name': {'$regex': new RegExp("Whepcrack", 'i')}}) + }}); + } } empty_card(type) { @@ -58,9 +84,8 @@ export default class Board extends Base { // {'event': 'action'} makeChange = (change) => { - // seriously calling without a change? if (!change) return; - console.log("board ->", change); // TODO remove + console.log(change); let action = change.action; switch (change.event) { @@ -90,6 +115,15 @@ export default class Board extends Base { break; } + // loads the card data into a blank space + // used when initial card loading, + // or when bringing back cards from discard + case "load": { + // action: {space, type, data} + this.spaces[action.space][action.type][0].data = action.data; + break; + } + // add a card to a space case "add": { // action: {space, type, card} @@ -146,17 +180,13 @@ export default class Board extends Base { this.movement.source = (-1); } - // TODO - canAttack(id) { - return false; - } - selectCard(e) { let id = parseInt(e.target.id.substr(1)); let type = (() => { switch (e.target.id.charAt(0)) { case 'b': return 'battlegear'; case 'c': return 'creatures'; + case 'm': return 'mirage'; default: return ""; } })(); @@ -167,54 +197,21 @@ export default class Board extends Base { } if (Phase.phase == "movement" && type == "creatures") { - // TODO combat // TODO if self selection, check activated ability if (this.movement.source == id) { return this.resetCardSelection(); } // moving into a space if (this.movement.source > 0) { + // TODO combat this.submitChange({event: "movement", action: {src: this.movement.source, dest: id}}); // Only one move per turn this.spaces[id].creatures[0].moveable = false; - return this.resetCardSelection(); + this.resetCardSelection(); + return; } - // don't select a blank space - if (this.spaces[id].creatures[0].data == null) return; - - // can't select opposing creatures - if (this.spaces[id].creatures[0].controlled == false) return; - - // select a card - let src_card = this.spaces[id].creatures[0]; - this.movement.source = id; - - let {valid, attackable} = this.movement.moves(this.spaces, id); - - // set selectable options - this.spaces.forEach((space, i) => { - space.battlegear.forEach((card) => { - card.selectable = false; - }); - let des_card = space.creatures[0]; - des_card.selectable = ((src_id, dest_id) => { - // If already moved, no valid movement - if (!src_card.moveable) return false; - // No self space movement - else if (src_id == dest_id) return false; - // If its a valid spot - else if (valid.includes(dest_id)) return true; - // TODO different effect for attackable? - else if (attackable.includes(dest_id) && this.canAttack()) return true; - // if all else its probably not moveable - else return false; - })(id, i); - }); - - // creature is now selected - this.spaces[id].creatures[0].selected = true; - this.spaces[id].creatures[0].selectable = false; + this.spaces = this.movement.moves(this.spaces, id); } } diff --git a/src/components/play/Gui.js b/src/components/play/Gui.js index d744bf5..3c172bf 100644 --- a/src/components/play/Gui.js +++ b/src/components/play/Gui.js @@ -15,7 +15,7 @@ export default class Gui extends Base { } endTurn() { - this.props.submitChange({event: "active", action: true}, "board"); + this.submitChange({event: "active", action: true}, "board"); } render() { @@ -25,7 +25,7 @@ export default class Gui extends Base {
- Hand + Player {this.props.player} Hand
diff --git a/src/components/play/Lobby.js b/src/components/play/Lobby.js new file mode 100644 index 0000000..40bef67 --- /dev/null +++ b/src/components/play/Lobby.js @@ -0,0 +1,41 @@ +import React from 'react'; +import Base from './_base'; +import {observable} from "mobx"; +import {observer, inject} from 'mobx-react'; + + +@observer +export default class Board extends Base { + @observable o_id = ""; + + constructor(props) { + super(props); + } + + single() { + this.props.submitChange({event: "single", action: "load"}, "game"); + } + + connect(e) { + if (this.o_id == this.props.uid) { + return; // TODO + } + this.props.submitChange({event: "connect", action: this.o_id}, "game"); + } + + render() { + return( +
+ You're id is {this.props.uid} +

+
+ this.o_id = e.target.value} /> + +
+

+ +
+ ); + } + +} diff --git a/src/components/play/Turn.js b/src/components/play/Turn.js new file mode 100644 index 0000000..60b7680 --- /dev/null +++ b/src/components/play/Turn.js @@ -0,0 +1,39 @@ +import React from 'react'; +import Base from './_base'; +import {observable} from "mobx"; +import {observer, inject} from 'mobx-react'; +import Phase from './rules/phase'; + +@observer +export default class Turn extends Base { + activeplayer = 0; + + constructor(props) { + super(props); + } + + // {'event': 'action'} + makeChange = (change) => { + if (!change) return; + console.log('turn ->', change); + // TODO + let action = change.action; + switch (change.event) { + + // change the game's phase + case "phase": { + // action: "string" + Phase.phase = action; + break; + } + default: + break; + } + } + + render() { + // class doesn't have gui + return(
); + } + +} diff --git a/src/components/play/_base.js b/src/components/play/_base.js index da4dc30..c3232a7 100644 --- a/src/components/play/_base.js +++ b/src/components/play/_base.js @@ -10,9 +10,9 @@ export default class Base extends React.Component { // It preforms the change locally and propegates it higher // So that a network listener can reflect the change on the other client // Local changes only can simply use "makeChange" - submitChange = (change) => { - this.props.submitChange(change); - this.makeChange(change); + submitChange = (change, destination) => { + this.props.submitChange(change, destination); + if (!destination) this.makeChange(change); // self change } makeChange = (change) => { diff --git a/src/components/play/index.js b/src/components/play/index.js index 6054799..42bea22 100644 --- a/src/components/play/index.js +++ b/src/components/play/index.js @@ -1,11 +1,13 @@ import React from 'react'; -import {observable} from "mobx"; +import {observable, reaction} from "mobx"; import {observer, inject} from 'mobx-react'; +import Peer from 'peerjs'; import API from '../SpreadsheetData'; import {Loading} from '../Snippets'; import Board from './Board'; import Gui from './Gui'; -import {Turn} from './rules/turn'; +import Lobby from './Lobby'; +import Turn from './Turn'; import '../../scss/play/play.scss'; import '../../scss/play/gui.scss'; @@ -13,31 +15,62 @@ import '../../scss/play/gui.scss'; @inject((stores, props, context) => props) @observer export default class Play extends React.Component { @observable loaded = false; + @observable id = ""; + @observable player = -1; + @observable format = 6; constructor(props) { super(props); - const turn = new Turn(this.receiveTurnChange.bind(this)); - this.makeTurnChange = turn.makeChange; + this.setupNetworking(); + + // TODO, temperary + // const reaction1 = reaction( + // () => this.player, + // (player, reaction) => { + // console.log(player); + // this.loadgame(); + // reaction.dispose(); + // }, + // {delay: 500} // wait for component to load + // ); } - componentDidUpdate() { - // todo remove / currently simulating - this.makeBoardChange({event:"active", action: true}); - this.makeTurnChange({event: "phase", action: "movement"}); + setupNetworking() { + let peer = new Peer(); + + peer.on('open', (id) => { + this.id = id; + }); + + peer.on('connection', (conn) => { + if (this.conn) return; + this.conn = conn; + this.conn.on("open", () => { + this.player = 2; // TODO properly assign player + this.conn.on('data', this.receiveNetworkChange.bind(this)); + }); + }); + + this.peer = peer; } sendNetworkChange(change, destination) { - // TODO implement network change - console.log("network ->", destination, change); + // If in single player mode, ignore network sends + if (this.player < 1) return; + // console.log("->", destination, change); + this.conn.send({change: change, destination: destination}); } // wrapper function - receiveNetworkChange(change, destination) { - this.recieveChange(change, destination); + receiveNetworkChange(packet) { + console.log("<-", packet.destination, packet.change); + this.recieveChange(packet.change, packet.destination); } // Parent function to handle any intercomponent or remote commands // send the command to that destination + // When receiving a change that does not include a destination, + // it is assumed that the component has handled its internal state recieveChange(change, destination) { switch(destination) { case "board": @@ -49,6 +82,12 @@ export default class Play extends React.Component { case "turn": this.makeTurnChange(change); break; + case "lobby": + this.makeLobbyChange(change); + break; + case "game": + this.makeChange(change); + break; default: throw "invalid destination"; } @@ -59,15 +98,51 @@ export default class Play extends React.Component { this.sendNetworkChange(change, destination || "gui"); } - // When handling movement, flip the card's owner before sending network - // Flip the board's numbers receiveBoardChange(change, destination) { if (destination) this.recieveChange(change, destination); - this.sendNetworkChange(change, destination || "board"); + let ch = JSON.parse(JSON.stringify(change)); // make "copy" + // When handling movement, flip the board's numbers before sending network + if (typeof(ch.action) === 'object') { + if (ch.action.hasOwnProperty('space')) { + ch.action.space = (this.format * 2 - ch.action.space) - 1; + } + else if (ch.action.hasOwnProperty('src')) { + ch.action.src = (this.format * 2 - ch.action.src) - 1; + ch.action.dest = (this.format * 2 - ch.action.dest) - 1; + } + } + this.sendNetworkChange(ch, destination || "board"); } receiveTurnChange(change, destination) { + if (destination) this.recieveChange(change, destination); + this.sendNetworkChange(change, destination || "turn"); + } + recieveLobbyChange(change, destination) { + this.recieveChange(change, destination || "lobby"); + } + + // for switching between lobby and games + makeChange(change) { + if (!change) return; + let action = change.action; + switch (change.event) { + case "single": { + this.player = 0; + break; + } + case "connect": { + this.conn = this.peer.connect(change.action); + this.conn.on("open", () => { + this.player = 1; + this.conn.on("data", this.receiveNetworkChange.bind(this)); + }); + break; + } + default: + break; + } } render() { @@ -77,15 +152,32 @@ export default class Play extends React.Component { return (); } + if (this.player < 0) return (
+ {if (n) this.makeLobbyChange = n.makeChange}} + uid={this.id} + /> +
+ ); + else + return ( +
+ {if (n) this.makeTurnChange = n.makeChange}} + /> {if (n) this.makeGuiChange = n.makeChange}} + player={this.player} /> {if (n) this.makeBoardChange = n.makeChange}} + player={this.player} />
); diff --git a/src/components/play/rules/movement.js b/src/components/play/rules/movement.js index 25981cc..7605fa4 100644 --- a/src/components/play/rules/movement.js +++ b/src/components/play/rules/movement.js @@ -24,7 +24,22 @@ export default class Movement extends Rule { super(); } + // TODO + canAttack(id) { + return false; + } + moves(spaces, id) { + // don't select a blank space + if (spaces[id].creatures[0].data == null) return spaces; + + // can't select opposing creatures + if (spaces[id].creatures[0].controlled == false) return spaces; + + // select a card + let src_card = spaces[id].creatures[0]; + this.source = id; + // Calculate range of spaces based on swift let swift = ((data) => { let s = (new RegExp(/swift ([0-9]+)/gi).exec(data.gsx$ability)); @@ -78,6 +93,30 @@ export default class Movement extends Rule { // remove invalid spaces valid = valid.filter(x => !invalid.includes(x)); - return {valid: valid, attackable: attackable}; + // set selectable options + spaces.forEach((space, i) => { + space.battlegear.forEach((card) => { + card.selectable = false; + }); + let des_card = space.creatures[0]; + des_card.selectable = ((src_id, dest_id) => { + // If already moved, no valid movement + if (!src_card.moveable) return false; + // No self space movement + else if (src_id == dest_id) return false; + // If its a valid spot + else if (valid.includes(dest_id)) return true; + // TODO different effect for attackable? + else if (attackable.includes(dest_id) && this.canAttack()) return true; + // if all else its probably not moveable + else return false; + })(id, i); + }); + + // creature is now selected + spaces[id].creatures[0].selected = true; + spaces[id].creatures[0].selectable = false; + + return spaces; } } diff --git a/src/components/play/rules/phase.js b/src/components/play/rules/phase.js new file mode 100644 index 0000000..a610813 --- /dev/null +++ b/src/components/play/rules/phase.js @@ -0,0 +1,18 @@ +import Rule from './_rule'; + +class Phase_Singleton extends Rule { + phase = "none"; + + // Singleton + static getInstance() { + if (!this.instance) { this.instance = new Phase_Singleton(); } + return this.instance; + } + + constructor() { + super(); + this.phase = "game start"; + } + +} +export default Phase_Singleton.getInstance(); diff --git a/src/components/play/rules/turn.js b/src/components/play/rules/turn.js deleted file mode 100644 index df86257..0000000 --- a/src/components/play/rules/turn.js +++ /dev/null @@ -1,48 +0,0 @@ -import Rule from './_rule'; - -export class Turn extends Rule { - activeplayer = 0; - - constructor(receiveTurnChange, player1, player2) { - super(); - this.submitChange = receiveTurnChange; - } - - // {'event': 'action'} - makeChange = (change) => { - if (!change) return; - console.log('turn ->', change); - // TODO - let action = change.action; - switch (change.event) { - - // change the game's phase - case "phase": { - // action: "string" - Phase.phase = action; - break; - } - default: - break; - } - } - -} - -class Phase_Singleton extends Rule { - phase = "none"; - - // Singleton - static getInstance() { - if (!this.instance) { this.instance = new Phase_Singleton(); } - return this.instance; - } - - constructor() { - super(); - this.phase = "game start"; - } - -} -const Phase = Phase_Singleton.getInstance(); -export {Phase}; diff --git a/src/scss/play/lobby.scss b/src/scss/play/lobby.scss new file mode 100644 index 0000000..7f19acb --- /dev/null +++ b/src/scss/play/lobby.scss @@ -0,0 +1 @@ +@import 'shared';