[add] client connections, lobby

This commit is contained in:
Daniel
2019-01-02 00:02:32 -05:00
parent c9511c6c7c
commit 78c3b075b8
10 changed files with 306 additions and 127 deletions

View File

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

View File

@@ -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 {
<div className="location"><img src="https://drive.google.com/uc?id=0B6oyUfwoM3u1SHJ5ejJPTk85THM" /></div>
</div>
<div className="hand">
<span className="card_name">Hand</span>
<span className="card_name">Player {this.props.player} Hand</span>
<div className="attacks">
<div></div>
<div></div>

View File

@@ -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(
<div className="lobby">
<span>You're id is {this.props.uid}</span>
<br /><br />
<form>
<input type="text" name="opponent's id" value={this.o_id} onChange={(e) => this.o_id = e.target.value} />
<input type="button" value="Connect" onClick={this.connect.bind(this)} />
</form>
<br /><br />
<button onClick={this.single.bind(this)}>Single Player</button>
</div>
);
}
}

View File

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

View File

@@ -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) => {

View File

@@ -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 (<Loading />);
}
if (this.player < 0)
return (
<div className="play">
<Lobby
submitChange={this.recieveLobbyChange.bind(this)}
ref={n => {if (n) this.makeLobbyChange = n.makeChange}}
uid={this.id}
/>
</div>
);
else
return (
<div className="play">
<Turn
submitChange={this.receiveTurnChange.bind(this)}
ref={n => {if (n) this.makeTurnChange = n.makeChange}}
/>
<Gui
submitChange={this.recieveGuiChange.bind(this)}
ref={n => {if (n) this.makeGuiChange = n.makeChange}}
player={this.player}
/>
<Board
submitChange={this.receiveBoardChange.bind(this)}
ref={n => {if (n) this.makeBoardChange = n.makeChange}}
player={this.player}
/>
</div>
);

View File

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

View File

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

View File

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

1
src/scss/play/lobby.scss Normal file
View File

@@ -0,0 +1 @@
@import 'shared';