diff --git a/client/public/assets/gfx/sheep.png b/client/public/assets/gfx/sheep.png
new file mode 100755
index 0000000..ff93c3b
Binary files /dev/null and b/client/public/assets/gfx/sheep.png differ
diff --git a/client/src/App.js b/client/src/App.js
index 839b692..83fc084 100755
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -28,10 +28,12 @@ import { Winner } from "./Winner.js";
import { HouseRules } from "./HouseRules.js";
import { Dice } from "./Dice.js";
import { assetsPath } from "./Common.js";
+import { Sheep } from "./Sheep.js";
import history from "./history.js";
import "./App.css";
import equal from "fast-deep-equal";
+import { purple } from "@material-ui/core/colors";
/*
const Pip = () => {
{
setDirection(Math.floor(birdAngles * newAngle / 360.));
}, [time, setCell, speed, rotation, setDirection]);
- useEffect(() => {
-
- }, [angle]);
-
return
{
}}/>
;
+ } else if (tile.type === 'sheep') {
+ div =
+
;
} else {
div =
{
+ // Use useRef for mutable variables that we want to persist
+ // without triggering a re-render on their change
+ const requestRef = React.useRef();
+
+ const animate = time => {
+ callback(time)
+ requestRef.current = requestAnimationFrame(animate);
+ }
+
+ React.useEffect(() => {
+ requestRef.current = requestAnimationFrame(animate);
+ return () => cancelAnimationFrame(requestRef.current);
+ }, []); // Make sure the effect runs only once
+}
+
+const Sheep = ({ radius, speed, size, style }) => {
+ const [time, setTime] = useState(0);
+ const [direction, setDirection] = useState(Math.random() * 2 * Math.PI);
+ const [y, setY] = useState((Math.random() - 0.5) * radius);
+ const [frame, setFrame] = useState(0);
+ const [x, setX] = useState((Math.random() - 0.5) * radius);
+
+ const previousTimeRef = React.useRef();
+
+ useAnimationFrame(time => {
+ if (previousTimeRef.current !== undefined) {
+ const deltaTime = time - previousTimeRef.current;
+ previousTimeRef.current = time;
+ setTime(deltaTime);
+ } else {
+ previousTimeRef.current = time;
+ }
+ });
+
+ useEffect(() => {
+ let alpha = time / speed;
+ const sheepSpeed = 0.05;
+ if (alpha > 1.0) {
+ alpha = 0.1;
+ }
+ let newX = x + sheepSpeed * Math.sin(direction) * alpha,
+ newY = y + sheepSpeed * Math.cos(direction) * alpha;
+ if (Math.sqrt((newX * newX) + (newY * newY)) > Math.sqrt(radius * radius)) {
+ let newDirection = direction + Math.PI + 0.5 * (Math.random() - 0.5) * Math.PI;
+ while (newDirection >= 2 * Math.PI) {
+ newDirection -= 2 * Math.PI;
+ }
+ while (newDirection <= -2 * Math.PI) {
+ newDirection += 2 * Math.PI;
+ }
+ setDirection(newDirection);
+ newX += sheepSpeed * Math.sin(newDirection) * alpha;
+ newY += sheepSpeed * Math.cos(newDirection) * alpha;
+ }
+ setX(newX);
+ setY(newY);
+ setFrame(frame + sheepSteps * alpha);
+ }, [time, speed, setDirection]);
+
+ const cell = Math.floor(frame) % sheepSteps;
+
+ return 0 ? +1 : -1}, 1)`,
+ ...style
+ }}
+ >
;
+};
+
+
+
+const Herd = ({count, style}) => {
+ const [sheep, setSheep] = useState([]);
+ useEffect(() => {
+ const tmp = [];
+ for (let i = 0; i < count; i++) {
+ const scalar = Math.random();
+ tmp.push()
+ }
+ setSheep(tmp);
+ }, [count, setSheep]);
+
+ return { sheep }
;
+};
+
+export { Sheep, Herd };
diff --git a/original/sheep-alpha.xcf b/original/sheep-alpha.xcf
new file mode 100755
index 0000000..346169f
Binary files /dev/null and b/original/sheep-alpha.xcf differ
diff --git a/original/sheep.png b/original/sheep.png
new file mode 100755
index 0000000..ff93c3b
Binary files /dev/null and b/original/sheep.png differ
diff --git a/server/ai/app.js b/server/ai/app.js
index da5bbff..c7d9206 100644
--- a/server/ai/app.js
+++ b/server/ai/app.js
@@ -1,5 +1,6 @@
const fetch = require('node-fetch');
const WebSocket = require('ws');
+const fs = require('fs').promises;
const version = '0.0.1';
@@ -18,7 +19,7 @@ For example:
const server = process.argv[2];
const gameId = process.argv[3];
let session = undefined;
-const user = process.argv[4];
+const name = process.argv[4];
const game = {};
@@ -30,19 +31,29 @@ const error = (e) => {
const connect = async () => {
let loc = new URL(server), new_uri;
- const res = await fetch(`${server}/api/v1/games`, {
- method: 'GET',
- cache: 'no-cache',
- credentials: 'same-origin', /* include cookies */
- headers: {
- 'Content-Type': 'application/json'
+ let player;
+ try {
+ const data = JSON.parse(await fs.readFile(`${name}.json`, 'utf-8'));
+ player = data.player;
+ } catch (_) {
+ const res = await fetch(`${server}/api/v1/games`, {
+ method: 'GET',
+ cache: 'no-cache',
+ credentials: 'same-origin', /* include cookies */
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+ if (!res) {
+ throw new Error(`Unable to connect to ${server}`);
}
- });
- if (!res) {
- throw new Error(`Unable to connect to ${server}`);
- }
- const { player } = JSON.parse(await res.text());
+ player = JSON.parse(await res.text()).player;
+ await fs.writeFile(`${name}.json`, JSON.stringify({
+ name,
+ player
+ }));
+ }
console.log(`Connecting to ${server} as ${player}`);
if (loc.protocol === "https:") {
@@ -68,52 +79,264 @@ const connect = async () => {
resolve(ws);
};
+ const connection = (ws) => {
+ console.log("connection request cookie: ", ws.upgradeReq.headers.cookie);
+ };
+
const close = (e) => {
console.log(`ws - close`);
};
ws.on('open', open);
+ ws.on('connect', () => { connect(ws); });
ws.on('headers', headers);
ws.on('close', close);
ws.on('error', error);
- ws.on('message', (data) => { message(ws, data) });
+ ws.on('message', (data) => { message(ws, data); });
});
};
+const createPlayer = (ws) => {
+ const send = (data) => {
+ ws.send(JSON.stringify(data));
+ };
+
+ if (game.name === '') {
+ send({ type: 'player-name', name });
+ return;
+ }
+
+ if (game.state !== 'lobby') {
+ return;
+ }
+
+ if (game.unselected.indexOf(name) === -1) {
+ return;
+ }
+
+ const slots = [];
+ for (let color in game.players) {
+ if (game.players[color].status === 'Not active') {
+ slots.push(color);
+ }
+ }
+ if (slots.length === 0) {
+ return;
+ }
+ const index = Math.floor(Math.random() * slots.length);
+ console.log(`Requesting to play as ${slots[index]}.`);
+ game.unselected = game.unselected.filter(
+ color => color === slots[index]);
+ send({
+ type: 'set',
+ field: 'color',
+ value: slots[index]
+ });
+ send({
+ type: 'chat',
+ message: `Woohoo! Robot AI ${version} is alive!`
+ });
+};
+
+const tryBuild = (ws) => {
+ const send = (data) => {
+ console.log(`ws - send`);
+ ws.send(JSON.stringify(data));
+ };
+ let trying = false;
+ if (game.private.wood
+ && game.private.brick
+ && game.private.sheep
+ && game.private.wheat) {
+ send({
+ type: 'buy-settlement'
+ });
+ trying = true;
+ }
+
+ if (game.private.wood && game.private.brick) {
+ send({
+ type: 'buy-road'
+ });
+ trying = true;
+ }
+
+ return trying;
+};
+
const message = (ws, data) => {
+ const send = (data) => {
+ console.log(`ws - send: ${data.type}`);
+ ws.send(JSON.stringify(data));
+ };
+
data = JSON.parse(data);
switch (data.type) {
case 'game-update':
+ console.log(`ws - receive - `,
+ Object.assign({}, data.update, {
+ activities: 'filtered out',
+ chat: 'filtered out'
+ })
+ );
+
Object.assign(game, data.update);
- if (game.name === '') {
- ws.send(JSON.stringify({ type: 'player-name', name: user }));
- }
- if (game.state === 'lobby' && game.unselected.indexOf(user) !== -1) {
- const slots = [];
- for (let color in game.players) {
- if (game.players[color].status === 'Not active') {
- slots.push(color);
+ console.log(`state - ${game.state}`);
+
+ switch (game.state) {
+ case undefined:
+ case 'lobby':
+ createPlayer(ws);
+ break;
+
+ case 'game-order':
+ if (!game.color) {
+ console.log(`game-order - player not active`);
+ return;
+ }
+ console.log(`game-order - `, {
+ color: game.color,
+ players: game.players
+ });
+ if (!game.players[game.color].orderRoll) {
+ console.log(`Time to roll as ${game.color}`);
+ send({ type: 'roll' });
+ }
+ break;
+
+ case 'initial-placement': {
+ console.log({ color: game.color, state: game.state, turn: game.turn });
+ if (game.turn.color !== game.color) {
+ break;
+ }
+
+ let index;
+ const type = game.turn.actions[0];
+ if (type === 'place-road') {
+ console.log({ roads: game.turn.limits.roads });
+ index = game.turn.limits.roads[Math.floor(
+ Math.random() * game.turn.limits.roads.length)];
+ } else if (type === 'place-settlement') {
+ console.log({ corners: game.turn.limits.corners });
+ index = game.turn.limits.corners[Math.floor(
+ Math.random() * game.turn.limits.corners.length)];
+ }
+ console.log(`Selecting ${type} at ${index}`);
+ send({
+ type, index
+ });
+ } break;
+
+ case 'normal':
+ if (game.turn.color !== game.color) {
+ return;
+ }
+ if (game.turn.actions && game.turn.actions.indexOf('place-road') !== -1) {
+ index = game.turn.limits.roads[Math.floor(
+ Math.random() * game.turn.limits.roads.length)];
+ send({
+ type: 'place-road', index
+ });
+ return;
+ }
+
+ if (game.turn.actions && game.turn.actions.indexOf('place-settlement') !== -1) {
+ console.log({ corners: game.turn.limits.corners });
+ index = game.turn.limits.corners[Math.floor(
+ Math.random() * game.turn.limits.corners.length)];
+ send({
+ type: 'place-settlement', index
+ });
+ return;
+ }
+
+ if (!game.dice) {
+ console.log(`Rolling...`);
+ send({
+ type: 'roll'
+ });
+ return;
+ }
+
+ if (game.private.mustDiscard) {
+ let mustDiscard = game.private.mustDiscard;
+ const cards = [],
+ discards = {};
+ const types = ['wheat', 'sheep', 'stone', 'brick', 'wood'];
+ types.forEach(type => {
+ for (let i = 0; i < game.private[type]; i++) {
+ cards.push(type);
+ }
+ });
+ while (mustDiscard--) {
+ const type = cards[Math.floor(Math.random() * cards.length)];
+ if (!(type in discards)) {
+ discards[type] = 1;
+ } else {
+ discards[type]++;
+ }
+ }
+ console.log(`discarding - `, discards);
+ send({
+ type: 'discard',
+ discards
+ });
+ return;
+ }
+
+ if (game.turn.actions
+ && game.turn.actions.indexOf('place-robber') !== -1) {
+ console.log({ pips: game.turn.limits.pips });
+ const index = game.turn.limits.pips[Math.floor(Math.random() * game.turn.limits.pips.length)];
+ console.log(`placing robber - ${index}`)
+ send({
+ type: 'place-robber',
+ index
+ });
+ return;
+ }
+
+ if (game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1) {
+ const { color } = game.turn.limits.players[Math.floor(Math.random() * game.turn.limits.players.length)];
+ console.log(`stealing resouce from ${game.players[color].name}`);
+ send({
+ type: 'steal-resource',
+ color
+ });
+ return;
+ }
+
+ if (game.turn.robberInAction) {
+ console.log({ turn: game.turn });
+ } else {
+ console.log({
+ turn: game.turn,
+ wheat: game.private.wheat,
+ sheep: game.private.sheep,
+ stone: game.private.stone,
+ brick: game.private.brick,
+ wood: game.private.wood,
+ });
+ if (!tryBuild(ws)) {
+ send({
+ type: 'pass'
+ });
}
}
- if (slots.length !== 0) {
- const index = Math.floor(Math.random() * slots.length);
- console.log(`Requesting to play as ${slots[index]}.`);
- game.unselected = game.unselected.filter(
- color => color === slots[index]);
- ws.send(JSON.stringify({
- type: 'set',
- field: 'color',
- value: slots[index]
- }));
- ws.send(JSON.stringify({
- type: 'chat',
- message: `Woohoo! Robot AI ${version} is alive!`
- }));
- }
- }
+ break;
+ default:
+ console.log({ state: game.state, turn: game.turn });
+ break;
+ }
break;
case 'ping':
+ if (!game.state) {
+ console.log(`ping received with no game. Sending update request`);
+ ws.send(JSON.stringify({
+ type: 'game-update'
+ }));
+ }
break;
default:
@@ -123,14 +346,10 @@ const message = (ws, data) => {
}
const ai = async (ws) => {
- ws.send(JSON.stringify({
- type: 'game-update'
- }));
}
connect().then((ws) => {
- ai(ws).then(() => {
- })
+ ai(ws)
.catch((error) => {
console.error(error);
ws.close();
diff --git a/server/routes/games.js b/server/routes/games.js
index 8ec7834..011b4da 100755
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -753,7 +753,7 @@ const canGiveBuilding = (game) => {
}
}
-const adminActions = (game, action, value, query) => {
+const adminCommands = (game, action, value, query) => {
let color, player, parts, session, corners, error;
switch (action) {
@@ -962,6 +962,15 @@ const adminActions = (game, action, value, query) => {
return;
}
return `Unable to find active session for ${colorToWord(color)} (${value})`;
+ case "state":
+ if (game.state !== 'lobby') {
+ return `Game already started.`;
+ }
+ if (game.active < 2) {
+ return `Not enough players in game to start.`;
+ }
+ game.state = 'game-order';
+ break;
default:
return `Invalid admin action ${action}.`;
@@ -1864,7 +1873,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
if (req.headers['private-token'] !== req.app.get('admin')) {
error = `Invalid admin credentials.`;
} else {
- error = adminActions(game, action, value, req.query);
+ error = adminCommands(game, action, value, req.query);
}
if (!error) {
sendGameToPlayers(game);
@@ -2500,21 +2509,25 @@ const playCard = (game, session, card) => {
const placeSettlement = (game, session, index) => {
const player = session.player;
index = parseInt(index);
+
if (game.state !== 'initial-placement' && game.state !== 'normal') {
- return `You cannot purchase a development card unless the game is active (${game.state}).`;
+ return `You cannot place a settlement unless the game is active (${game.state}).`;
}
+
if (session.color !== game.turn.color) {
return `It is not your turn! It is ${game.turn.name}'s turn.`;
}
+ /* index out of range... */
if (game.placements.corners[index] === undefined) {
return `You have requested to place a settlement illegally!`;
}
+
/* If this is not a valid road in the turn limits, discard it */
- if (game.turn
- && game.turn.limits
- && game.turn.limits.corners
- && game.turn.limits.corners.indexOf(index) === -1) {
+ if (!game.turn
+ || !game.turn.limits
+ || !game.turn.limits.corners
+ || game.turn.limits.corners.indexOf(index) === -1) {
return `You tried to cheat! You should not try to break the rules.`;
}
const corner = game.placements.corners[index];
@@ -2650,19 +2663,26 @@ const placeRoad = (game, session, index) => {
const player = session.player;
index = parseInt(index);
if (game.state !== 'initial-placement' && game.state !== 'normal') {
- return `You cannot purchase a development card unless the game is active (${game.state}).`;
+ return `You cannot purchase a place a road unless the game is active (${game.state}).`;
}
+
if (session.color !== game.turn.color) {
return `It is not your turn! It is ${game.turn.name}'s turn.`;
}
+ /* Valid index location */
if (game.placements.roads[index] === undefined) {
return `You have requested to place a road illegally!`;
}
+
/* If this is not a valid road in the turn limits, discard it */
- if (game.turn && game.turn.limits && game.turn.limits.roads && game.turn.limits.roads.indexOf(index) === -1) {
+ if (!game.turn
+ || !game.turn.limits
+ || !game.turn.limits.roads
+ || game.turn.limits.roads.indexOf(index) === -1) {
return `You tried to cheat! You should not try to break the rules.`;
}
+
const road = game.placements.roads[index];
if (road.color) {
return `This location already has a road belonging to ${game.players[road.color].name}!`;
@@ -2930,6 +2950,9 @@ const discard = (game, session, discards) => {
}
sum += parseInt(discards[type]);
}
+ if (sum > player.mustDiscard) {
+ return `You can not discard that many cards! You can only discard ${player.mustDiscard}.`;
+ }
/*
if (sum !== player.mustDiscard) {
return `You need to discard ${player.mustDiscard} cards.`;
@@ -2943,13 +2966,13 @@ const discard = (game, session, discards) => {
player.resources -= count;
}
addChatMessage(game, null, `${session.name} discarded ${sum} resource cards.`);
- if (player.mustDiscard) {
+ if (player.mustDiscard > 0) {
addChatMessage(game, null, `${session.name} did not discard enough and must discard ${player.mustDiscard} more cards.`);
}
let move = true;
for (let color in game.players) {
- const discard = game.players[color].mustDiscard;
+ const discard = game.players[color].mustDiscard > 0;
if (discard) {
move = false;
}
@@ -3231,11 +3254,15 @@ const placeCity = (game, session, index) => {
if (session.color !== game.turn.color) {
return `It is not your turn! It is ${game.turn.name}'s turn.`;
}
+ /* Valid index check */
if (game.placements.corners[index] === undefined) {
return `You have requested to place a city illegally!`;
}
/* If this is not a placement the turn limits, discard it */
- if (game.turn && game.turn.limits && game.turn.limits.corners && game.turn.limits.corners.indexOf(index) === -1) {
+ if (!game.turn
+ || !game.turn.limits
+ || !game.turn.limits.corners
+ || game.turn.limits.corners.indexOf(index) === -1) {
return `You tried to cheat! You should not try to break the rules.`;
}
const corner = game.placements.corners[index];
@@ -4371,11 +4398,15 @@ router.ws("/ws/:id", async (ws, req) => {
resetDisconnectCheck(game, req);
console.log(`${short}: Game ${id} - WebSocket connect from ${getName(session)}`);
-
- if (session.keepAlive) {
+
+ /* Send initial ping to initiate communication with client */
+ if (!session.keepAlive) {
+ console.log(`${short}: Sending initial ping`);
+ ping(session);
+ } else {
clearTimeout(session.keepAlive);
+ session.keepAlive = setTimeout(() => { ping(session); }, 2500);
}
- session.keepAlive = setTimeout(() => { ping(session); }, 2500);
});
const debugChat = (game, preamble) => {