Skip to content

Commit

Permalink
Merge pull request #71 from cmavelis/dev
Browse files Browse the repository at this point in the history
Release v0.3.1
  • Loading branch information
cmavelis authored Nov 27, 2023
2 parents 17bc5dd + 25cfcb1 commit a450261
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 33 deletions.
61 changes: 55 additions & 6 deletions server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,66 @@ const { koaBody } = require('koa-body');
const { Sequelize } = require('sequelize');
const axios = require('axios');

const makeMatchURL = ({
matchID,
playerID,
}: {
matchID: string;
playerID: 0 | 1;
}) => {
return `${process.env.HOST_URL}/match/${matchID}?player=${playerID + 1}`;
};

const sequelize = new Sequelize(process.env.DATABASE_URL, {
dialect: 'postgres',
});
const db = new PostgresStore(process.env.DATABASE_URL as string, {
dialect: 'postgres',
});

const Game = db.sequelize.model('Match');

// notify players when it's their turn
Game.beforeUpsert(async (created) => {
const { id } = created;
const oldMatch = await Game.findByPk(id);
const oldActivePlayers = oldMatch?.state?.ctx.activePlayers;
const newActivePlayers = created?.state?.ctx.activePlayers;

if (!(oldActivePlayers && newActivePlayers)) {
return;
}

for (const p of [0, 1]) {
const oldPhase = oldActivePlayers[p];
const newPhase = newActivePlayers[p];
if (oldPhase === newPhase) {
continue;
}
const player = oldMatch.players[p];
if (!player.isConnected) {
// send discord message
User.findOne({ where: { name: player.name } }).then((user) => {
botClient.users
.send(
user.discordUser.id,
`It's your turn: \n ${makeMatchURL({
matchID: created.id,
playerID: p as 0 | 1,
})}`,
)

.then(() =>
console.debug(
`discord message sent to ${user.discordUser.username} ${user.discordUser.id}`,
),
)
.catch(console.error);
});
}
}
});

const getDiscordTokenExchangeURI = (origin: string) => {
return origin + '/api/exchange/discord';
};
Expand Down Expand Up @@ -52,35 +105,31 @@ User.sync().catch(console.error);

interface ZugToken extends ZugUser {
iat: number; // 'instantiated at'
credentials: string;
}

const generateCredentials = async (ctx: {
request: { headers: { [x: string]: any } };
}) => {
console.log('request', ctx.request.headers);
const authHeader = ctx.request.headers['authorization'];
if (authHeader === 'open') {
return 'open';
}

const token: ZugToken = decodeToken(authHeader);
return token.credentials;
return token.authToken;
};

const authenticateCredentials = async (
credentials: string,
playerMetadata: any,
) => {
console.log('authenticateCredentials');
console.log(credentials, playerMetadata);
// this allows testing matches to work
if (playerMetadata?.credentials === 'open') {
return true;
}
if (credentials) {
const token: ZugToken = decodeToken(credentials);
if (token?.credentials === playerMetadata?.credentials) return true;
if (token?.authToken === playerMetadata?.credentials) return true;
}
return false;
};
Expand Down
6 changes: 4 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router';
import { store } from './store';
</script>

<template>
Expand All @@ -9,6 +10,7 @@ import { RouterLink, RouterView } from 'vue-router';
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/how-to-play">How To Play</RouterLink>
<RouterLink to="/about">About</RouterLink>
<span>{{ store.zugUsername }}</span>
</nav>
</div>
</header>
Expand All @@ -29,13 +31,13 @@ nav {
margin-bottom: 1rem;
}
nav a {
nav * {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
nav *:first-child {
border: 0;
}
</style>
33 changes: 8 additions & 25 deletions src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,18 @@
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}

/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);

--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);

--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);

--section-gap: 160px;
}

@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);

--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);

--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);

--color-theme-green: hsla(160, 100%, 37%, 1)
}
--color-theme-green: hsla(160, 100%, 37%, 1)
}

*,
Expand Down
Binary file added src/assets/zug-a-zug-ah.mp3
Binary file not shown.
Binary file added src/assets/zug-zug.mp3
Binary file not shown.
11 changes: 11 additions & 0 deletions src/composables/useWindowFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ref } from 'vue';

export const useWindowFocus = () => {
const windowHasFocus = ref(document.hasFocus());

setInterval(function () {
windowHasFocus.value = document.hasFocus();
}, 500);

return windowHasFocus;
};
12 changes: 12 additions & 0 deletions src/utils/notificationSound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// import sound from '../assets/zug-zug.mp3';
// import benSound from '../assets/zug-a-zug-ah.mp3';

export const getNotificationSound = async (isBen: boolean) => {
let sound;
if (isBen) {
sound = await import('../assets/zug-a-zug-ah.mp3');
} else {
sound = await import('../assets/zug-zug.mp3');
}
return sound.default;
};
20 changes: 20 additions & 0 deletions src/utils/titleAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const originalDocumentTitle = document.title;
let interval1: string | number | NodeJS.Timeout | undefined,
interval2: string | number | NodeJS.Timeout | undefined;

export const startTitleNotification = (newTitle: string) => {
interval1 = setInterval(() => {
document.title = newTitle;
}, 2500);
setTimeout(() => {
interval2 = setInterval(() => {
document.title = originalDocumentTitle;
}, 2500);
}, 1250);
};

export const stopTitleNotification = () => {
clearInterval(interval1);
clearInterval(interval2);
document.title = originalDocumentTitle;
};
45 changes: 45 additions & 0 deletions src/views/MatchView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@ import { isEqual } from 'lodash';
import BoardComponent from '@/components/BoardComponent.vue';
import BoardDisplay from '@/components/BoardDisplay.vue';
import { useWindowFocus } from '@/composables/useWindowFocus';
import { SimulChessClient } from '@/game/App';
import type { GObject } from '@/game/Game';
import { store } from '@/store';
import { getNotificationSound } from '@/utils/notificationSound';
import {
startTitleNotification,
stopTitleNotification,
} from '@/utils/titleAnimation';
const windowHasFocus = useWindowFocus();
watch(windowHasFocus, (newFocus) => {
if (newFocus) {
stopTitleNotification();
}
});
onMounted(() => {
window.addEventListener('keydown', keyListener);
});
Expand Down Expand Up @@ -127,6 +142,7 @@ function setHistoryStep(value: number) {
historyTurnStep.value = value;
}
// new turn watcher
watch(
() => gameState.G.history,
async (newHistory, oldHistory) => {
Expand All @@ -137,13 +153,39 @@ watch(
},
);
// TODO: add something like this to show ooponent phase
const gamePhase = computed(() => {
if (gameState.ctx.activePlayers) {
return gameState.ctx.activePlayers[playerID.value] || '?';
} else {
return 'end';
}
});
const opponentWaiting = computed(() => {
if (!gameState.ctx.activePlayers) {
return false;
}
const opponentPlayerID = playerID.value === 1 ? 0 : 1;
return gameState.ctx.activePlayers[opponentPlayerID] === 'resolution';
});
// "your turn" sound
getNotificationSound(store.zugUsername === 'Ben').then((notificationSound) => {
const audio = new Audio(notificationSound);
audio.volume = 0.75;
watch(
() => gamePhase.value,
(newPhase, oldPhase) => {
if (newPhase !== oldPhase && oldPhase === 'resolution') {
if (!windowHasFocus.value) {
audio.play();
startTitleNotification('Your turn!');
}
}
},
);
});
</script>

<template>
Expand Down Expand Up @@ -172,6 +214,9 @@ const gamePhase = computed(() => {
<p v-if="gamePhase === 'resolution'" class="info-message">
Waiting for opponent to finish turn...
</p>
<p v-if="opponentWaiting" class="info-message">
Your opponent is waiting for you to finish...
</p>
<BoardComponent
v-if="gameStateLoaded"
:client="matchClientOne.client"
Expand Down

0 comments on commit a450261

Please sign in to comment.