This commit is contained in:
Jose Conde 2024-07-17 22:52:07 +02:00
parent 4e75c3af77
commit 54bd7f3840
18 changed files with 242 additions and 79 deletions

24
.hmrc
View File

@ -1,9 +1,11 @@
{
"path": "G:\\Other\\Development\\Projects\\[ideas]\\domino-client",
"name": "domino-client",
"initialVersion": "0.1.0",
"version": "0.1.0",
"initialVersion": "0.1.4",
"version": "0.1.4",
"docker": {
"useRegistry": true,
"registry": "192.168.1.115:5000",
"repository": "arhuako/domino-client"
},
"repository": {
@ -12,16 +14,15 @@
"manage": true
},
"changelog": {
"create": true,
"managed": true,
"createHTML": true,
"htmlPath": "public"
},
"_backupInitial": {
"name": "domino-client",
"version": "0.0.0",
"version": "0.1.0",
"private": true,
"type": "module",
"type": "commonjs",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
@ -30,7 +31,12 @@
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
"format": "prettier --write src/",
"docker-build": "docker build -t arhuako/domino-client:latest .",
"docker-tag": "docker tag arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.1.0",
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.1.0",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
"serve": "npm run build-only && http-server ./dist -c-1"
},
"dependencies": {
"@pixi/sound": "^6.0.0",
@ -60,6 +66,7 @@
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"http-server": "^14.1.1",
"jsdom": "^24.1.0",
"npm-run-all2": "^6.2.0",
"prettier": "^3.2.5",
@ -68,6 +75,11 @@
"vite": "^5.3.1",
"vitest": "^1.6.0",
"vue-tsc": "^2.0.21"
},
"author": "arhuako",
"reposityory": {
"type": "git",
"url": "https://gitea.xintanalabs.net/arhuako/domino-client"
}
}
}

View File

@ -1,5 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
## Unreleased
Initial commit
### Added
- Initial commit
- Match page back button
- Team play
- Movement synchronized netween clients and AI players
### Fixed
- Button statuses

View File

@ -1,7 +1,37 @@
# Create the container from the alpine linux image
#FROM alpine:3.7
FROM node:22-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm i
# Add nginx and nodejs
#RUN apk add --update nginx nodejs
RUN apk add --update nginx
# Create the directories we will need
RUN mkdir -p /tmp/nginx/vue-single-page-app
RUN mkdir -p /var/log/nginx
RUN mkdir -p /var/www/html
# Copy the respective nginx configuration files
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
# Set the directory we want to run the next commands for
WORKDIR /tmp/nginx/vue-single-page-app
COPY . .
EXPOSE 8080
CMD ["npm", "run", "serve"]
# Copy our source code into the container
# Install the dependencies, can be commented out if you're running the same node version
RUN npm install
# run webpack and the vue-loader
RUN npm run build-only
# copy the built app to our served directory
RUN cp -r dist/* /var/www/html
# make all files belong to the nginx user
RUN chown nginx:nginx /var/www/html
# start nginx and keep the process from backgrounding and the container from quitting
CMD ["nginx", "-g", "daemon off;"]

6
nginx/default.conf Normal file
View File

@ -0,0 +1,6 @@
server {
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
}

23
nginx/nginx.conf Normal file
View File

@ -0,0 +1,23 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile off;
keepalive_timeout 60;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}

View File

@ -1,6 +1,6 @@
{
"name": "domino-client",
"version": "0.1.0",
"version": "0.1.3",
"private": true,
"type": "commonjs",
"scripts": {
@ -12,11 +12,11 @@
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"docker-build": "docker build -t arhuako/domino-client:latest .",
"docker-tag": "docker tag arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.1.0",
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.1.0",
"docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-client:latest .",
"docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-client:latest 192.168.1.115:5000/arhuako/domino-client:0.1.4",
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-client:latest && docker push 192.168.1.115:5000/arhuako/domino-client:0.1.4",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push",
"serve": "npm run build-only && http-server ./dist -c-1"
"serve": "npm run build-only && http-server ./dist -c-1 -s "
},
"dependencies": {
"@pixi/sound": "^6.0.0",

View File

@ -3,6 +3,7 @@ import { inject, onMounted, onUnmounted } from 'vue'
import { RouterView } from 'vue-router'
import type { AuthenticationService } from './services/AuthenticationService'
import { useEventBusStore } from './stores/eventBus'
import { sound } from '@pixi/sound'
const auth: AuthenticationService = inject<AuthenticationService>('auth') as AuthenticationService
auth.fromStorage()
@ -17,8 +18,38 @@ const handleBeforeUnload = (evt: any) => {
// console.log('location.href :>> ', location.pathname)
}
// document.addEventListener('visibilitychange', () => {
// console.log('visibilitychange')
// let playingOnHide = false
// if (document.hidden) {
// playingOnHide = true
// sound.pauseAll()
// } else {
// // Page became visible! Resume playing if audio was "playing on hide"
// if (playingOnHide) {
// sound.resumeAll()
// }
// }
// })
// const soundContextResume = () => {
// const context = sound.context.audioContext
// if (context.state === 'suspended' || context.state === 'interrupted') {
// context.resume()
// }
// }
// document.addEventListener('click', function (event) {
// console.log('click document :>> ', event)
// console.log('screen :>> ', screen)
// // if (event.target instanceof HTMLButtonElement) {
// // sound.play('click')
// // }
// })
onMounted(() => {
window.addEventListener('beforeunload', handleBeforeUnload)
// window.addEventListener('focus', soundContextResume)
})
onUnmounted(() => {

View File

@ -1,9 +1,17 @@
:root {
/* bulma color variables */
--bulma-primary-h: 40deg;
--bulma-primary-s: 48%;
--bulma-primary-l: 48%;
--bulma-info-h: 168deg;
--bulma-info-s: 58%;
--bulma-info-l: 28%;
--bulma-primary-l: 38%;
--bulma-link-h: 36deg;
--bulma-link-s: 19%;
--bulma-link-l: 16%;
--bulma-info-h: 192deg;
--bulma-info-l: 34%;
--bulma-success-s: 52%;
--bulma-success-l: 38%;
--bulma-warning-h: 58deg;
--bulma-warning-s: 61%;
--bulma-warning-l: 41%;
--bulma-danger-s: 74%;
--bulma-danger-l: 37%;
}

View File

@ -3,11 +3,20 @@ export const DEFAULT_CONTAINER_OPTIONS = {
height: 100,
x: 0,
y: 0,
visible: true
visible: true,
}
export const ORIENTATION_ANGLES: { [key: string]: number } = {
north: 0,
east: Math.PI / 2,
south: Math.PI,
west: (3 * Math.PI) / 2
west: (3 * Math.PI) / 2,
}
export const DIRECTION_INDEXES: { [key: string]: number } = {
north: 0,
east: 1,
south: 2,
west: 3,
}
export const DIRECTIONS = ['north', 'east', 'south', 'west']

View File

@ -99,8 +99,6 @@ export async function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export const DIRECTIONS = ['north', 'east', 'south', 'west']
export function isTilePair(tile: TileDto): boolean {
return !!(tile.pips && tile.pips[0] === tile.pips[1])
}

View File

@ -2,11 +2,11 @@ import { Application, Container, EventEmitter, Text, Ticker } from 'pixi.js'
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
import type { AnimationOptions, Movement, PlayerDto, TileDto } from '@/common/interfaces'
import { Tile } from '@/game/Tile'
import { DIRECTIONS, createContainer, isTilePair } from '@/common/helpers'
import { createContainer, isTilePair } from '@/common/helpers'
import { createText } from '@/game/utilities/fonts'
import { LoggingService } from '@/services/LoggingService'
import { GlowFilter } from 'pixi-filters'
import { ORIENTATION_ANGLES } from '@/common/constants'
import { DIRECTION_INDEXES, DIRECTIONS, ORIENTATION_ANGLES } from '@/common/constants'
import type { OtherHand } from './OtherHand'
import { sound } from '@pixi/sound'
import { t } from '@/i18n'
@ -181,6 +181,8 @@ export class Board extends EventEmitter {
const tileDto = tile.toPlain()
let direction = move.type === 'left' ? this.leftDirection : this.rightDirection
move.direction = this.hasSpaceToMove(move)
if (this.tiles.length === 0) {
x = 0
y = 0
@ -592,6 +594,22 @@ export class Board extends EventEmitter {
return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
}
hasSpaceToMove(move: Movement): string | undefined {
if (move.tile === undefined || move.direction === undefined) {
return undefined
}
const nextValidMoves = this.nextTileValidMoves(move.tile, move.type)
let index = DIRECTION_INDEXES[move.direction]
let valid = nextValidMoves[index]
while (!valid && index < nextValidMoves.length) {
index++
valid = nextValidMoves[index % nextValidMoves.length]
}
return DIRECTIONS[index]
}
clean() {
this.tiles = []
this.boneyard = []

View File

@ -81,8 +81,7 @@ export class Game extends EventEmitter {
iniialStuff(app: Application) {
app.stage.addChild(this.backgroundLayer)
const background = new Sprite(Assets.get(`bg-${this.options.background}`))
background.width = this.app.canvas.width
background.height = this.app.canvas.height
this.backgroundLayer.addChild(background)
}

View File

@ -44,5 +44,7 @@
"starting_game": "Starting game...",
"your-turn": "Your turn!",
"player-turn": "{0}'s turn!"
}
},
"back": "Back",
"session-name": "Session Name"
}

View File

@ -20,7 +20,7 @@
"welcome-to-the-user-username-s-home-page": "Bienvenido a la página de inicio de {0}",
"available-sessions": "Sesiones disponibles",
"background-color": "Color de fondo",
"blue-fabric": "Fabrica azul",
"blue-fabric": "Tela azul",
"cancel": "Cancelar",
"copy": "Copiar",
"game": {
@ -44,5 +44,7 @@
"seed": "Semilla",
"seed-session-seed": "Semilla: {0}",
"start": "Comenzar",
"yellow-fabric": "Tela amarilla"
"yellow-fabric": "Tela amarilla",
"back": "Volver",
"session-name": "Nombre de la sesión"
}

View File

@ -1,9 +1,8 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import '../node_modules/bulma/css/bulma.css'
import './assets/main.css'
import App from './App.vue'
import router from './router'

View File

@ -51,7 +51,7 @@ const router = createRouter({
],
},
{
path: '/game:id',
path: '/game/:id',
component: AuthenticatedLayout,
children: [
{

View File

@ -15,7 +15,7 @@ let background = ref<string>('green')
let teamed = ref<boolean>(false)
let pointsToWin = ref<number>(100)
let seed = ref<string>('')
let sessionName = ref('Test Value')
let sessionName = ref(`Test #${Date.now()}`)
let matchSessions = ref<MatchSessionDto[]>([])
let dataInterval: any
@ -130,7 +130,6 @@ async function deleteMatch(id: string) {
async function loadData() {
const listResponse = await gameService.listMatchSessions()
matchSessions.value = listResponse.data
sessionName.value = `Test #${Date.now()}`
}
onMounted(() => {
@ -156,7 +155,7 @@ function copy(sessionSeed: string) {
</h1>
<div class="block" v-if="!isSessionStarted">
<div class="field">
<label class="label">{{ $t('name') }}</label>
<label class="label">{{ $t('session-name') }}</label>
<div class="control">
<input
type="text"
@ -174,7 +173,7 @@ function copy(sessionSeed: string) {
class="input"
style="margin-bottom: 0"
v-model="seed"
placeholder="$t('seed-placeholder')"
:placeholder="$t('seed-placeholder')"
/>
</div>
</div>
@ -226,53 +225,64 @@ function copy(sessionSeed: string) {
</button>
</div>
</div>
<div class="buttons" v-if="isSessionStarted">
<button class="button" @click="setPlayerReady">
<span v-if="!readyForStart">{{ $t('ready') }}</span
><span v-else>{{ $t('unready') }}</span>
</button>
<button class="button" @click="startMatch" v-if="amIHost && readyForStart">
<span>{{ $t('start') }}</span>
</button>
<div class="block" v-if="isSessionStarted">
<h2 class="title is-4">{{ sessionState?.name }}</h2>
<h6 class="title is-size-5">Players</h6>
<div v-for="player in sessionState?.players" :key="player.id">
<p>{{ player.name }}</p>
<p>{{ player.ready ? 'Ready' : 'Not ready' }}</p>
</div>
<div class="buttons mt-6">
<button class="button" @click="setPlayerReady">
<span v-if="!readyForStart">{{ $t('ready') }}</span
><span v-else>{{ $t('unready') }}</span>
</button>
<button class="button" @click="startMatch" v-if="amIHost && readyForStart">
<span>{{ $t('start') }}</span>
</button>
<button class="button" @click="cancelMatch">
<span>{{ $t('cancel') }}</span>
</button>
<button class="button" @click="cancelMatch">
<span>{{ $t('cancel') }}</span>
</button>
</div>
</div>
</section>
<section class="section available-sessions">
<section class="section available-sessions" v-if="!isSessionStarted">
<h2 class="title is-4">{{ $t('available-sessions') }}</h2>
<div class="block">
<div v-if="matchSessions.length === 0">
<p>{{ $t('no-sessions-available') }}</p>
</div>
<div v-else class="grid is-col-min-12">
<div class="cell" v-for="session in matchSessions" :key="session.id">
<div class="card">
<div class="card-content">
<p class="title is-6">{{ session.name }}</p>
<p>{{ $t('id-session-_id', [session._id]) }}</p>
<p>{{ $t('players-session-players-length', [session.players.length]) }}</p>
<p>
{{ $t('seed-session-seed', [session.seed]) }}
<button class="button is-small" @click="() => copy(session.seed)">
{{ $t('copy') }}
</button>
</p>
<p>{{ $t('status-session-status', [session.status]) }}</p>
<div class="buttons is-centered mt-4"></div>
</div>
<div class="card-footer">
<p class="card-footer-item">
<a href="#" @click.once.prevent="() => joinMatch(session._id)">
{{ $t('join') }}
</a>
</p>
<p class="card-footer-item">
<a href="#" @click.once.prevent="() => deleteMatch(session._id)">
{{ $t('delete') }}
</a>
</p>
<div v-else class="fixed-grid has-3-cols">
<div class="grid">
<div class="cell" v-for="session in matchSessions" :key="session.id">
<div class="card">
<div class="card-content">
<p class="title is-6">{{ session.name }}</p>
<p>{{ $t('id-session-_id', [session._id]) }}</p>
<p>{{ $t('players-session-players-length', [session.players.length]) }}</p>
<p>
{{ $t('seed-session-seed', [session.seed]) }}
<button class="button is-small is-ghost" @click="() => copy(session.seed)">
{{ $t('copy') }}
</button>
</p>
<p>{{ $t('status-session-status', [session.status]) }}</p>
<div class="buttons is-centered mt-6">
<button
class="button is-primary"
@click.once.prevent="() => joinMatch(session._id)"
>
<span>{{ $t('join') }}</span>
</button>
<button
class="button is-text"
@click.once.prevent="() => deleteMatch(session._id)"
>
<span>{{ $t('delete') }}</span>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -73,6 +73,11 @@ onBeforeMount(() => {
</div>
</div>
</div>
<div class="buttons">
<button class="button is-primary" @click="router.push({ name: 'home' })">
{{ $t('back') }}
</button>
</div>
<div class="section">
<!-- <div>{{ matchSession }}</div> -->
</div>