initial commit
This commit is contained in:
@ -1,86 +1,9 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--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-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--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);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* 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%;
|
||||
}
|
||||
|
BIN
src/assets/images/circle.png
Normal file
BIN
src/assets/images/circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
@ -1,33 +1 @@
|
||||
/* @import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
} */
|
||||
@import './base.css';
|
||||
|
@ -1,120 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import type { GameState, PlayerState } from '@/utilities/interfaces'
|
||||
import { onMounted, ref, type PropType } from 'vue'
|
||||
import { assets } from '@/utilities/assets'
|
||||
import type { GameSessionState, GameState, PlayerState } from '@/utilities/interfaces'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { Game } from '@/utilities/Game'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
|
||||
import { Assets, Application as PixiApplication, Container, Sprite, Texture } from 'pixi.js'
|
||||
import { Scale } from '@/utilities/scale'
|
||||
import Hand from '@/utilities/Hand'
|
||||
import { playerState as mockPS } from '@/utilities/mocks'
|
||||
import { Board } from '@/utilities/Board'
|
||||
|
||||
const emit = defineEmits(['tileClick', 'passClick', 'leftClick', 'rightClick'])
|
||||
defineProps({
|
||||
gameState: Object as PropType<GameState>,
|
||||
playerState: Object as PropType<PlayerState>
|
||||
const emit = defineEmits(['move'])
|
||||
const props = defineProps({
|
||||
playerId: String
|
||||
})
|
||||
// console.log('d1 :>> ', d1)
|
||||
// console.log('t1 :>> ', t1)
|
||||
|
||||
// const tile = new Sprite(t1)
|
||||
const gameStore = useGameStore()
|
||||
|
||||
let appEl = ref<HTMLElement | null>(null)
|
||||
const app: PixiApplication = new PixiApplication()
|
||||
let hand: Hand
|
||||
let board: Board
|
||||
const game = new Game(
|
||||
{
|
||||
width: 1200,
|
||||
height: 650,
|
||||
boardScale: 0.8,
|
||||
handScale: 1
|
||||
},
|
||||
emit,
|
||||
props
|
||||
)
|
||||
|
||||
const scaleX = Scale([-16, 16], [0, 800])
|
||||
const scaleY = Scale([-12, 12], [0, 600])
|
||||
watch(
|
||||
() => gameStore.canMakeMove,
|
||||
(value: boolean) => {
|
||||
game.setCanMakeMove(value)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => gameStore.gameState,
|
||||
(value: GameState | undefined) => {
|
||||
if (value === undefined) return
|
||||
game.board.setState(value, props.playerId ?? '')
|
||||
// console.log('gameState-------------------------------------- :>> ', value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.sessionState,
|
||||
(value: GameSessionState | undefined) => {
|
||||
if (value === undefined) return
|
||||
// console.log('gameSessionState-------------------------------------- :>> ', value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.playerState,
|
||||
(value: PlayerState | undefined) => {
|
||||
if (value === undefined) return
|
||||
game.hand.update(value as PlayerState)
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await setup()
|
||||
await preload()
|
||||
if (appEl.value === null) return
|
||||
const canvas = await game.setup()
|
||||
appEl.value.appendChild(canvas)
|
||||
await game.preload()
|
||||
await game.start()
|
||||
|
||||
await start()
|
||||
// console.log('appEl :>> ', appEl.value)
|
||||
// console.log('mounted')
|
||||
// if (appEl.value === null) return
|
||||
|
||||
// console.log('App initialized')
|
||||
// console.log('loading assets')
|
||||
// const t1 = await Assets.load([d1])
|
||||
// console.log(`${d1} loaded`)
|
||||
// console.log('assets loaded')
|
||||
// const tile = new Sprite(t1)
|
||||
// tile.anchor.set(0.5)
|
||||
// tile.width = 50
|
||||
// tile.height = 100
|
||||
// tile.x = app.screen.width / 2
|
||||
// tile.y = app.screen.height / 2
|
||||
// app.stage.addChild(tile)
|
||||
// mockMove(game, [6, 6], 'left')
|
||||
// mockMove(game, [6, 4], 'left')
|
||||
// mockMove(game, [4, 4], 'left')
|
||||
// mockMove(game, [4, 0], 'left')
|
||||
// mockMove(game, [0, 0], 'left')
|
||||
// mockMove(game, [2, 0], 'left')
|
||||
// mockMove(game, [2, 2], 'left')
|
||||
// mockMove(game, [6, 5], 'right')
|
||||
// mockMove(game, [5, 5], 'right')
|
||||
// mockMove(game, [5, 1], 'right')
|
||||
// mockMove(game, [3, 1], 'right')
|
||||
// mockMove(game, [3, 0], 'right')
|
||||
// mockMove(game, [5, 0], 'right')
|
||||
})
|
||||
|
||||
async function start() {
|
||||
console.log('start')
|
||||
console.log('scaleX(0) :>> ', scaleX(0))
|
||||
console.log('scaleY(0) :>> ', scaleY(0))
|
||||
const boardContainer: Container = new Container()
|
||||
const handContainer: Container = new Container()
|
||||
app.stage.addChild(boardContainer)
|
||||
app.stage.addChild(handContainer)
|
||||
|
||||
hand = new Hand(handContainer, app.canvas)
|
||||
board = new Board(boardContainer, app.canvas)
|
||||
|
||||
hand.setTiles(mockPS as PlayerState)
|
||||
|
||||
hand.on('tileClick', (tile) => {
|
||||
emit('tileClick', tile)
|
||||
})
|
||||
|
||||
hand.on('passClick', () => {
|
||||
emit('passClick')
|
||||
})
|
||||
|
||||
hand.on('leftClick', () => {
|
||||
emit('tileClick')
|
||||
})
|
||||
|
||||
hand.on('rightClick', () => {
|
||||
emit('rightClick')
|
||||
})
|
||||
|
||||
app.ticker.add((time) => {
|
||||
// hand.update(time)
|
||||
// board.update(time)
|
||||
})
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
await app.init({ width: 800, height: 600 })
|
||||
if (appEl.value === null) return
|
||||
appEl.value.appendChild(app.canvas)
|
||||
}
|
||||
|
||||
async function preload() {
|
||||
console.log('loading assets')
|
||||
await Assets.load(assets)
|
||||
console.log('assets loaded')
|
||||
}
|
||||
onUnmounted(() => {
|
||||
game.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="appEl" class="game-container"></div>
|
||||
<!-- <Application :width="800" :height="600">
|
||||
<Loader :resources="resources">
|
||||
<template #fallback="{ progress }">
|
||||
<text :x="120" :y="120" :anchor="0.5">
|
||||
{{ `Loading... ${progress}` }}
|
||||
</text>
|
||||
</template>
|
||||
<template #default="{}"> </template>
|
||||
</Loader>
|
||||
</Application> -->
|
||||
<div ref="appEl" class="game-app"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.game-container {
|
||||
.game-app {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ function getPipClasses(value: number | undefined): string[] {
|
||||
|
||||
return positions[value]
|
||||
}
|
||||
console.log('props.tile :>> ', props.tile)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -18,7 +18,7 @@ function logout() {
|
||||
<div class="authenticated-layout">
|
||||
<header>
|
||||
<nav>
|
||||
<button @click="logout">Logout</button>
|
||||
<!-- <button @click="logout">Logout</button> -->
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
|
@ -3,16 +3,18 @@ import './assets/main.css'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import '../node_modules/bulma/css/bulma.css'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import socketService from '@/services/socket'
|
||||
import { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.provide('socket', socketService)
|
||||
app.provide('socket', new SocketIoClientService('http://localhost:3000'))
|
||||
|
||||
app.mount('#app')
|
||||
|
58
src/managers/SocketIoEventManager.ts
Normal file
58
src/managers/SocketIoEventManager.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { wait } from '@/utilities/helpers'
|
||||
import type { GameSessionState } from '@/utilities/interfaces'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
export class SocketIoEventManager {
|
||||
gameStore: any = useGameStore()
|
||||
|
||||
handleSessionStateEvent(data: GameSessionState) {
|
||||
const { updateSessionState } = this.gameStore
|
||||
updateSessionState(data)
|
||||
return {
|
||||
status: 'ok'
|
||||
}
|
||||
}
|
||||
|
||||
handleGameStateEvent(data: any) {
|
||||
const { updateGameState } = this.gameStore
|
||||
updateGameState(data)
|
||||
return {
|
||||
status: 'ok'
|
||||
}
|
||||
}
|
||||
|
||||
handlePlayerStateEvent(data: any) {
|
||||
const { updatePlayerState } = this.gameStore
|
||||
updatePlayerState(data)
|
||||
return {
|
||||
status: 'ok'
|
||||
}
|
||||
}
|
||||
|
||||
async handleCanMakeMoveEvent(data: any) {
|
||||
const { canMakeMove, moveToMake } = storeToRefs(this.gameStore)
|
||||
const { updateCanMakeMove, setIncomingFreeEnds } = this.gameStore
|
||||
setIncomingFreeEnds(data.freeHands)
|
||||
updateCanMakeMove(true)
|
||||
while (canMakeMove.value) {
|
||||
await wait(500)
|
||||
}
|
||||
return {
|
||||
status: 'ok',
|
||||
...moveToMake.value
|
||||
}
|
||||
}
|
||||
|
||||
async handleCanSelectTileEvent() {
|
||||
const { canSelectTile } = storeToRefs(this.gameStore)
|
||||
const { updateCanSelectTile } = this.gameStore
|
||||
updateCanSelectTile(true)
|
||||
while (canSelectTile.value) {
|
||||
await wait(500)
|
||||
}
|
||||
return {
|
||||
status: 'ok'
|
||||
}
|
||||
}
|
||||
}
|
82
src/services/SocketIoClientService.ts
Normal file
82
src/services/SocketIoClientService.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import type { GameSessionState, GameState, PlayerState } from '@/utilities/interfaces'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
import { SocketIoEventManager } from '@/managers/SocketIoEventManager'
|
||||
|
||||
export class SocketIoClientService {
|
||||
public socket: Socket
|
||||
private isConnected = false
|
||||
private gameEventManager = new SocketIoEventManager()
|
||||
|
||||
constructor(url: string) {
|
||||
this.socket = io(url)
|
||||
this.addEvents()
|
||||
}
|
||||
|
||||
addEvents(): void {
|
||||
this.socket.on('connect', () => {
|
||||
this.isConnected = true
|
||||
if (this.socket && this.socket.recovered) {
|
||||
console.log('socket recovered succesfully')
|
||||
} else {
|
||||
console.log('socket connected')
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
this.isConnected = false
|
||||
console.log('Disconnected from server')
|
||||
})
|
||||
|
||||
this.socket.on('reconnect', () => {
|
||||
this.isConnected = true
|
||||
console.log('Reconnected to server')
|
||||
})
|
||||
|
||||
this.socket.on('reconnect_error', () => {
|
||||
this.isConnected = false
|
||||
console.log('Failed to reconnect to server')
|
||||
})
|
||||
|
||||
this.socket.on('sessionState', (data: GameSessionState, callback: any) => {
|
||||
callback(this.gameEventManager.handleSessionStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('gameState', (data: GameState, callback: any) => {
|
||||
callback(this.gameEventManager.handleGameStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('playerState', (data: PlayerState, callback: any) => {
|
||||
callback(this.gameEventManager.handlePlayerStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('makeMove', async (data: any, callback: any) => {
|
||||
callback(await this.gameEventManager.handleCanMakeMoveEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('chooseTile', async (data: any, callback: any) => {
|
||||
callback(await this.gameEventManager.handleCanSelectTileEvent())
|
||||
})
|
||||
}
|
||||
|
||||
sendMessage(event: string, data: any): void {
|
||||
if (this.isConnected) {
|
||||
this.socket?.emit(event, data)
|
||||
} else {
|
||||
console.log('Not connected to server')
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessageWithAck(event: string, data: any): Promise<any> {
|
||||
if (this.isConnected) {
|
||||
return await this.socket?.emitWithAck(event, data)
|
||||
} else {
|
||||
console.log('Not connected to server')
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.socket?.disconnect()
|
||||
this.isConnected = false
|
||||
console.log('Disconnected from server')
|
||||
}
|
||||
}
|
@ -3,17 +3,32 @@ import { io } from 'socket.io-client'
|
||||
const URL = 'http://localhost:3000'
|
||||
|
||||
process.env.NODE_ENV === 'production' ? undefined : 'http://localhost:3001'
|
||||
console.log('URL :>> ', URL)
|
||||
|
||||
const socket = URL === undefined ? io() : io(URL)
|
||||
const ioOpts = {
|
||||
// timeout: 300 * 1000,
|
||||
// reconnectionDelay: 5000 // defaults to 1000
|
||||
// reconnectionDelayMax: 10000 // defaults to 5000reconnection: true, // Enable reconnection
|
||||
reconnectionAttempts: Infinity, // Maximum number of reconnection attempts
|
||||
reconnectionDelay: 1000, // Time between each attempt (in ms)
|
||||
reconnectionDelayMax: 5000, // Maximum time between attempts (in ms)
|
||||
randomizationFactor: 0.5, // Randomization factor for the delay
|
||||
timeout: 20000 // Connection timeout (in ms)
|
||||
}
|
||||
// const socket = URL === undefined ? io(ioOpts) : io(URL, ioOpts)
|
||||
const socket = io('http://localhost:3000', ioOpts)
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('---> connected')
|
||||
if (socket.recovered) {
|
||||
console.log('socket recovered succesfully')
|
||||
} else {
|
||||
console.log('socket connected')
|
||||
}
|
||||
|
||||
// setTimeout(() => {
|
||||
// socket.io.engine.close()
|
||||
// }, 60 * 1000)
|
||||
})
|
||||
socket.onAny((event, msg) => {
|
||||
console.log('event :>> ', event)
|
||||
console.log('msg :>> ', msg)
|
||||
})
|
||||
console.log('help :>> ')
|
||||
|
||||
export default {
|
||||
socket,
|
||||
sendMessage(event: any, data: any) {
|
||||
|
58
src/stores/game.ts
Normal file
58
src/stores/game.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import type { GameSessionState, GameState, Movement, PlayerState } from '@/utilities/interfaces'
|
||||
|
||||
export const useGameStore = defineStore('game', () => {
|
||||
const sessionState = ref<GameSessionState | undefined>(undefined)
|
||||
const gameState = ref<GameState | undefined>(undefined)
|
||||
const playerState = ref<PlayerState | undefined>(undefined)
|
||||
const canMakeMove = ref(false)
|
||||
const canSelectTile = ref(false)
|
||||
const moveToMake = ref<Movement | undefined>(undefined)
|
||||
const incomingFreeEnds = ref<[number, number] | undefined>(undefined)
|
||||
|
||||
function updateSessionState(newState: GameSessionState) {
|
||||
sessionState.value = newState
|
||||
}
|
||||
|
||||
function updateGameState(newState: GameState) {
|
||||
gameState.value = newState
|
||||
}
|
||||
|
||||
function updatePlayerState(newState: PlayerState) {
|
||||
playerState.value = newState
|
||||
}
|
||||
|
||||
function updateCanMakeMove(value: boolean) {
|
||||
canMakeMove.value = value
|
||||
}
|
||||
|
||||
function setMoveToMake(move: Movement) {
|
||||
moveToMake.value = move
|
||||
}
|
||||
|
||||
function updateCanSelectTile(value: boolean) {
|
||||
canSelectTile.value = value
|
||||
}
|
||||
|
||||
function setIncomingFreeEnds(freeEnds: [number, number]) {
|
||||
incomingFreeEnds.value = freeEnds
|
||||
}
|
||||
|
||||
return {
|
||||
sessionState,
|
||||
gameState,
|
||||
playerState,
|
||||
canMakeMove,
|
||||
moveToMake,
|
||||
incomingFreeEnds,
|
||||
canSelectTile,
|
||||
updateSessionState,
|
||||
updateGameState,
|
||||
updatePlayerState,
|
||||
updateCanMakeMove,
|
||||
setMoveToMake,
|
||||
setIncomingFreeEnds,
|
||||
updateCanSelectTile
|
||||
}
|
||||
})
|
@ -1,143 +1,556 @@
|
||||
import { Container, EventEmitter, Graphics, Text } from 'pixi.js'
|
||||
import { Scale } from './scale'
|
||||
import type { GameState } from './interfaces'
|
||||
import type { Tile } from './Tile'
|
||||
import { getColorBackground } from './backgrounds'
|
||||
import { Application, Container, EventEmitter, Graphics, Text, Ticker } from 'pixi.js'
|
||||
import { Scale, type ScaleFunction } from './scale'
|
||||
import type { GameState, Movement, TileDto } from './interfaces'
|
||||
import { Tile } from './Tile'
|
||||
import { DIRECTIONS, createContainer, isTilePair, isTileVertical } from './helpers'
|
||||
import { createText } from './fonts'
|
||||
import { Dot } from './Dot'
|
||||
|
||||
export class Board extends EventEmitter {
|
||||
container: Container
|
||||
private _scale: number = 1
|
||||
private _canMove: boolean = false
|
||||
|
||||
ticker: Ticker
|
||||
height: number
|
||||
width: number
|
||||
scaleY: Function
|
||||
scaleX: Function
|
||||
grain: number = 25
|
||||
scaleY: ScaleFunction
|
||||
scaleX: ScaleFunction
|
||||
state: GameState | undefined
|
||||
initialContainer: Container = new Container()
|
||||
mainContainer: Container = new Container()
|
||||
leftContainer: Container = new Container()
|
||||
rightContainer: Container = new Container()
|
||||
container!: Container
|
||||
initialContainer!: Container
|
||||
tilesContainer!: Container
|
||||
leftContainer!: Container
|
||||
rightContainer!: Container
|
||||
interactionContainer!: Container
|
||||
textContainer!: Container
|
||||
tiles: Tile[] = []
|
||||
boneyard: Tile[] = []
|
||||
movements: any[] = []
|
||||
freeEnds?: [number, number]
|
||||
leftTile?: Tile
|
||||
rightTile?: Tile
|
||||
nextTile?: Tile
|
||||
textWaitForPlayers!: Text
|
||||
textYourTurn!: Text
|
||||
leftDirection: string = 'west'
|
||||
rightDirection: string = 'east'
|
||||
playerHand: Tile[] = []
|
||||
firstTile: Tile | undefined
|
||||
|
||||
constructor(container: Container, canvas: HTMLCanvasElement) {
|
||||
constructor(app: Application) {
|
||||
super()
|
||||
this.container = container
|
||||
this.height = canvas.height - 130
|
||||
this.width = canvas.width
|
||||
this.container.width = this.width
|
||||
this.container.height = this.height
|
||||
this.container.y = 0
|
||||
const scaleXSteps = Math.floor(this.width / 25)
|
||||
const scaleYSteps = Math.floor(this.height / 25)
|
||||
this.ticker = app.ticker
|
||||
this.height = app.canvas.height - 130
|
||||
this.width = app.canvas.width
|
||||
this.scaleX = Scale([0, this.width], [0, this.width])
|
||||
this.scaleY = Scale([0, this.height], [0, this.height])
|
||||
this.calculateScale()
|
||||
|
||||
this.container = createContainer({
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
parent: app.stage
|
||||
})
|
||||
|
||||
this.initialContainer = createContainer({
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
color: 0x1e2f23,
|
||||
visible: false,
|
||||
parent: this.container
|
||||
})
|
||||
|
||||
this.tilesContainer = createContainer({
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
color: 0x1e2f23,
|
||||
parent: this.container
|
||||
})
|
||||
|
||||
this.interactionContainer = createContainer({
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
parent: this.container,
|
||||
visible: false
|
||||
})
|
||||
|
||||
const verticalLine = new Graphics()
|
||||
.moveTo(this.scaleX(0), 0)
|
||||
.lineTo(this.scaleX(0), this.height)
|
||||
.stroke(0xff0000)
|
||||
const horizontalLine = new Graphics()
|
||||
.moveTo(0, this.scaleY(0))
|
||||
.lineTo(this.width, this.scaleY(0))
|
||||
.stroke(0xff0000)
|
||||
verticalLine.alpha = 0.2
|
||||
horizontalLine.alpha = 0.2
|
||||
this.tilesContainer.addChild(verticalLine)
|
||||
this.tilesContainer.addChild(horizontalLine)
|
||||
|
||||
this.createTexts()
|
||||
}
|
||||
|
||||
private calculateScale() {
|
||||
const scaleXSteps = Math.floor(this.width / (this.grain * this.scale)) / 2
|
||||
const scaleYSteps = Math.floor(this.height / (this.grain * this.scale)) / 2
|
||||
this.scaleX = Scale([-scaleXSteps, scaleXSteps], [0, this.width])
|
||||
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
||||
|
||||
this.initialContainer = new Container()
|
||||
this.initialContainer.addChild(
|
||||
new Text({
|
||||
text: 'Initial Board',
|
||||
style: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
fill: 0xff1010,
|
||||
align: 'center'
|
||||
}
|
||||
})
|
||||
)
|
||||
this.container.addChild(this.initialContainer)
|
||||
this.mainContainer = new Container()
|
||||
this.mainContainer.addChild(
|
||||
new Text({
|
||||
text: 'Main Board',
|
||||
style: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
fill: 0xff1010,
|
||||
align: 'center'
|
||||
}
|
||||
})
|
||||
)
|
||||
this.container.addChild(this.mainContainer)
|
||||
this.createLeftRightContainers()
|
||||
this.initialContainer.visible = false
|
||||
this.mainContainer.visible = false
|
||||
}
|
||||
|
||||
private createLeftRightContainers() {
|
||||
console.log('this.height :>> ', this.height)
|
||||
this.leftContainer.x = 0
|
||||
this.leftContainer.y = 0
|
||||
this.leftContainer.width = 50
|
||||
this.leftContainer.height = this.height
|
||||
this.leftContainer.eventMode = 'static'
|
||||
this.leftContainer.cursor = 'pointer'
|
||||
|
||||
const leftSide = new Graphics()
|
||||
.rect(0, 0, 50, this.height)
|
||||
// Draw the wheel
|
||||
.fill({ color: 0x848484 })
|
||||
leftSide.alpha = 0
|
||||
this.leftContainer.addChild(leftSide)
|
||||
this.leftContainer.on('pointerover', () => {
|
||||
leftSide.alpha = 0.5
|
||||
})
|
||||
this.leftContainer.on('pointerout', () => {
|
||||
leftSide.alpha = 0
|
||||
})
|
||||
this.leftContainer.on('pointerdown', () => {
|
||||
this.emit('leftClick')
|
||||
})
|
||||
|
||||
this.leftContainer.addChild(
|
||||
new Text({
|
||||
text: 'Left',
|
||||
style: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
fill: 0xff1010,
|
||||
align: 'center'
|
||||
}
|
||||
})
|
||||
)
|
||||
//
|
||||
this.rightContainer.x = this.width - 50
|
||||
this.rightContainer.y = 0
|
||||
this.rightContainer.width = 50
|
||||
this.rightContainer.height = this.height
|
||||
this.rightContainer.eventMode = 'static'
|
||||
this.rightContainer.cursor = 'pointer'
|
||||
|
||||
const rightSide = new Graphics()
|
||||
.rect(0, 0, 50, this.height)
|
||||
// Draw the wheel
|
||||
.fill({ color: 0x848484 })
|
||||
rightSide.alpha = 0
|
||||
this.rightContainer.addChild(rightSide)
|
||||
this.rightContainer.on('pointerover', () => {
|
||||
rightSide.alpha = 0.5
|
||||
})
|
||||
this.rightContainer.on('pointerout', () => {
|
||||
rightSide.alpha = 0
|
||||
})
|
||||
this.rightContainer.on('pointerdown', () => {
|
||||
this.emit('rightClick')
|
||||
})
|
||||
this.rightContainer.addChild(
|
||||
new Text({
|
||||
text: 'Right',
|
||||
style: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
fill: 0xff1010,
|
||||
align: 'center'
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
this.container.addChild(this.leftContainer)
|
||||
this.container.addChild(this.rightContainer)
|
||||
get count() {
|
||||
return this.tiles.length
|
||||
}
|
||||
|
||||
setState(state: GameState) {
|
||||
get scale() {
|
||||
return this._scale
|
||||
}
|
||||
|
||||
set scale(value: number) {
|
||||
this._scale = value
|
||||
this.calculateScale()
|
||||
}
|
||||
|
||||
get canMove() {
|
||||
return this._canMove
|
||||
}
|
||||
|
||||
set canMove(value: boolean) {
|
||||
this._canMove = value
|
||||
this.updateCanMoveText()
|
||||
}
|
||||
|
||||
setPlayerHand(tiles: Tile[]) {
|
||||
this.playerHand = tiles
|
||||
}
|
||||
|
||||
createTexts() {
|
||||
this.textContainer = new Container()
|
||||
this.textWaitForPlayers = createText('Waiting for players', this.scaleX(0), 100)
|
||||
this.textYourTurn = createText('Your turn!', this.scaleX(0), 100)
|
||||
|
||||
this.container.addChild(this.textContainer)
|
||||
this.textContainer.addChild(this.textWaitForPlayers)
|
||||
this.textContainer.addChild(this.textYourTurn)
|
||||
this.textYourTurn.visible = false
|
||||
}
|
||||
|
||||
private updateCanMoveText() {
|
||||
this.textWaitForPlayers.visible = !this.canMove
|
||||
this.textYourTurn.visible = this.canMove
|
||||
}
|
||||
|
||||
setState(state: GameState, playerId: string) {
|
||||
this.state = state
|
||||
const { lastMove } = state
|
||||
|
||||
if (lastMove === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
lastMove !== null &&
|
||||
lastMove.tile !== undefined &&
|
||||
lastMove.tile.pips !== undefined &&
|
||||
lastMove.playerId !== playerId
|
||||
) {
|
||||
const tile = new Tile(lastMove.tile.id, this.ticker, lastMove.tile.pips, this.scale)
|
||||
this.nextTile = tile
|
||||
lastMove.tile = tile.toPlain()
|
||||
this.movements.push(lastMove)
|
||||
this.addTile(tile, lastMove)
|
||||
this.setFreeEnd(lastMove)
|
||||
}
|
||||
}
|
||||
|
||||
getOrientationII(tile: TileDto, side: string) {
|
||||
let orientation = ''
|
||||
const isPair = isTilePair(tile)
|
||||
if (side === 'left') {
|
||||
if (this.freeEnds !== undefined && tile.pips !== undefined) {
|
||||
const isInverted = this.freeEnds[0] === tile.pips[1]
|
||||
if (this.leftDirection === 'east') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'east' : 'west'
|
||||
} else {
|
||||
orientation = 'north'
|
||||
}
|
||||
} else if (this.leftDirection === 'west') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'west' : 'east'
|
||||
} else {
|
||||
orientation = 'north'
|
||||
}
|
||||
} else if (this.leftDirection === 'north') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'north' : 'south'
|
||||
} else {
|
||||
orientation = 'west'
|
||||
}
|
||||
} else if (this.leftDirection === 'south') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'south' : 'north'
|
||||
} else {
|
||||
orientation = 'west'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (side === 'right') {
|
||||
if (this.freeEnds !== undefined && tile.pips !== undefined) {
|
||||
const isInverted = this.freeEnds[1] === tile.pips[0]
|
||||
if (this.rightDirection === 'east') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'west' : 'east'
|
||||
} else {
|
||||
orientation = 'north'
|
||||
}
|
||||
} else if (this.rightDirection === 'west') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'east' : 'west'
|
||||
} else {
|
||||
orientation = 'north'
|
||||
}
|
||||
} else if (this.rightDirection === 'north') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'south' : 'north'
|
||||
} else {
|
||||
orientation = 'west'
|
||||
}
|
||||
} else if (this.rightDirection === 'south') {
|
||||
if (!isPair) {
|
||||
orientation = isInverted ? 'north' : 'south'
|
||||
} else {
|
||||
orientation = 'west'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return orientation
|
||||
}
|
||||
|
||||
addTile(tile: Tile, move: Movement) {
|
||||
let orientation = ''
|
||||
let x: number =
|
||||
move.type === 'left'
|
||||
? this.scaleX.inverse(this.leftTile?.x ?? 0)
|
||||
: this.scaleX.inverse(this.rightTile?.x ?? 0)
|
||||
let y: number =
|
||||
move.type === 'left'
|
||||
? this.scaleY.inverse(this.leftTile?.y ?? 0)
|
||||
: this.scaleY.inverse(this.rightTile?.y ?? 0)
|
||||
const isLeft = move.type === 'left'
|
||||
const tileDto = tile.toPlain()
|
||||
let direction = move.type === 'left' ? this.leftDirection : this.rightDirection
|
||||
|
||||
if (this.tiles.length === 0) {
|
||||
x = 0
|
||||
y = 0
|
||||
if (tile.isPair()) {
|
||||
orientation = 'north'
|
||||
} else {
|
||||
orientation = 'east'
|
||||
}
|
||||
this.firstTile = tile
|
||||
} else if (move.direction !== undefined) {
|
||||
direction = move.direction
|
||||
const availableMoves = this.nextTileValidMoves(tileDto, move.type)
|
||||
const availablePositions = this.nextTileValidPoints(tileDto, move.type, availableMoves)
|
||||
const directionIndex = DIRECTIONS.indexOf(direction)
|
||||
isLeft ? (this.leftDirection = direction) : (this.rightDirection = direction)
|
||||
const availablePosition: [number, number] | undefined = availablePositions[directionIndex]
|
||||
orientation = this.getOrientationII(tileDto, move.type)
|
||||
availablePosition && ([x, y] = availablePosition)
|
||||
} else {
|
||||
if (this.tiles.length === 0) {
|
||||
x = 0
|
||||
y = 0
|
||||
if (tile.isPair()) {
|
||||
tile.setOrientation('north')
|
||||
} else {
|
||||
tile.setOrientation('east')
|
||||
}
|
||||
} else {
|
||||
const availableMoves = this.nextTileValidMoves(tileDto, move.type)
|
||||
const availablePositions = this.nextTileValidPoints(tileDto, move.type, availableMoves)
|
||||
|
||||
let directionIndex = DIRECTIONS.indexOf(direction)
|
||||
let availablePosition: [number, number] | undefined = availablePositions[directionIndex]
|
||||
let endlessLoop: number = 0
|
||||
while (endlessLoop < 4 && availablePosition === undefined) {
|
||||
directionIndex = (directionIndex + 1) % 4
|
||||
availablePosition = availablePositions[directionIndex]
|
||||
endlessLoop++
|
||||
}
|
||||
if (endlessLoop >= 4) {
|
||||
throw new Error('No available position')
|
||||
}
|
||||
direction = DIRECTIONS[directionIndex]
|
||||
isLeft ? (this.leftDirection = direction) : (this.rightDirection = direction)
|
||||
orientation = this.getOrientationII(tileDto, move.type)
|
||||
availablePosition && ([x, y] = availablePosition)
|
||||
}
|
||||
}
|
||||
const endTile = isLeft ? this.leftTile : this.rightTile
|
||||
const isEndVertical = endTile?.isVertical() ?? false
|
||||
const isNextVertical = orientation === 'north' || orientation === 'south'
|
||||
if (this.tiles.length > 0 && endTile !== undefined) {
|
||||
//!tile.equals(endTile)
|
||||
if (direction === 'east') {
|
||||
x += !isEndVertical && isNextVertical ? 0 : 1
|
||||
} else if (direction === 'west') {
|
||||
x -= !isEndVertical && isNextVertical ? 0 : 1
|
||||
} else if (direction === 'north') {
|
||||
y -= isEndVertical && !isNextVertical ? 0 : 1
|
||||
} else if (direction === 'south') {
|
||||
y += isEndVertical && !isNextVertical ? 0 : 1
|
||||
}
|
||||
}
|
||||
console.log('position::>>', tile.pips, x, y)
|
||||
tile.setPosition(this.scaleX(x), this.scaleY(y))
|
||||
tile.setOrientation(orientation)
|
||||
tile.reScale(this.scale)
|
||||
this.tiles.push(tile)
|
||||
tile.addTo(this.tilesContainer)
|
||||
}
|
||||
|
||||
getPlayedTile(id: string): Tile | undefined {
|
||||
return this.tiles.find((t) => t.id === id)
|
||||
}
|
||||
|
||||
getTileInHand(id: string): Tile | undefined {
|
||||
return this.playerHand.find((t) => t.id === id)
|
||||
}
|
||||
|
||||
setFreeEnd(move: Movement) {
|
||||
const { type, tile: tileDto } = move
|
||||
const tile = this.getPlayedTile(tileDto?.id ?? '')
|
||||
|
||||
if (tile === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { pips } = tile
|
||||
|
||||
if (pips === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.freeEnds === undefined) {
|
||||
this.leftTile = this.rightTile = tile
|
||||
if (tile.isPair()) {
|
||||
this.freeEnds = [pips[0], pips[1]]
|
||||
} else {
|
||||
this.freeEnds = [pips[1], pips[0]]
|
||||
}
|
||||
} else if (type === 'left') {
|
||||
this.leftTile = tile
|
||||
if (tile.isPair()) {
|
||||
this.freeEnds[0] = pips[0]
|
||||
} else {
|
||||
this.freeEnds[0] = pips[0] === this.freeEnds[0] ? pips[1] : pips[0]
|
||||
}
|
||||
} else if (type === 'right') {
|
||||
this.rightTile = tile
|
||||
if (tile.isPair()) {
|
||||
this.freeEnds[1] = pips[0]
|
||||
} else {
|
||||
this.freeEnds[1] = pips[0] === this.freeEnds[1] ? pips[1] : pips[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateBoard(move: Movement) {
|
||||
try {
|
||||
const { tile: tileDto } = move
|
||||
const tile = this.getTileInHand(tileDto?.id ?? '')
|
||||
|
||||
if (tile === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.movements.push(move)
|
||||
this.addTile(tile, move)
|
||||
this.setFreeEnd(move)
|
||||
} catch (error) {
|
||||
console.log('error :>> ', error)
|
||||
}
|
||||
}
|
||||
|
||||
setMovesForTile(values: [boolean, boolean], tile: TileDto) {
|
||||
this.setValidEnds(values, tile)
|
||||
}
|
||||
|
||||
setValidEnds(values: boolean[], tile: TileDto) {
|
||||
console.log('validEnds')
|
||||
if (this.count === 0) {
|
||||
this.createInteractionsII('right', [[0, 0], undefined, undefined, undefined])
|
||||
return
|
||||
}
|
||||
|
||||
if (values[0]) {
|
||||
const side = 'left'
|
||||
const validInteractions = this.nextTileValidMoves(tile, side)
|
||||
const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
|
||||
console.log('validInteractions :>> ', validInteractions)
|
||||
// this.createInteractions(side, tile)
|
||||
this.createInteractionsII(side, validPoints)
|
||||
}
|
||||
if (values[1]) {
|
||||
const side = 'right'
|
||||
const validInteractions = this.nextTileValidMoves(tile, side)
|
||||
const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
|
||||
console.log('validInteractions :>> ', validInteractions)
|
||||
this.createInteractionsII(side, validPoints)
|
||||
}
|
||||
}
|
||||
|
||||
nextTileValidPoints(
|
||||
tile: TileDto,
|
||||
side: string,
|
||||
validMoves: boolean[]
|
||||
): ([number, number] | undefined)[] {
|
||||
const isLeft = side === 'left'
|
||||
const end = isLeft ? this.leftTile : this.rightTile
|
||||
const isEndVertical = end?.isVertical() ?? false
|
||||
const direction = isLeft ? this.leftDirection : this.rightDirection
|
||||
const signX = direction === 'west' ? -1 : 1
|
||||
const signY = direction === 'south' ? 1 : -1
|
||||
const isEndPair = end?.isPair() ?? false
|
||||
|
||||
const x = this.scaleX.inverse(end?.x ?? 0)
|
||||
const y = this.scaleY.inverse(end?.y ?? 0)
|
||||
|
||||
let pointNorth: [number, number] | undefined = undefined
|
||||
let pointSouth: [number, number] | undefined = undefined
|
||||
let pointEast: [number, number] | undefined = undefined
|
||||
let pointWest: [number, number] | undefined = undefined
|
||||
|
||||
if (this.count !== 0) {
|
||||
if (validMoves[0]) {
|
||||
// north
|
||||
if (isEndVertical) {
|
||||
pointNorth = [x, y - 3]
|
||||
} else {
|
||||
pointNorth = isEndPair ? [x, y - 2] : [x + 1 * signX, y - 2]
|
||||
}
|
||||
}
|
||||
if (validMoves[2]) {
|
||||
// south
|
||||
if (isEndVertical) {
|
||||
pointSouth = [x, y + 3]
|
||||
} else {
|
||||
pointSouth = isEndPair ? [x, y + 2] : [x + 1 * signX, y + 2]
|
||||
}
|
||||
}
|
||||
if (validMoves[1]) {
|
||||
// east
|
||||
if (isEndVertical) {
|
||||
pointEast = isEndPair ? [x + 2, y] : [x + 2, y + 1 * signY]
|
||||
} else {
|
||||
pointEast = [x + 3, y]
|
||||
}
|
||||
}
|
||||
if (validMoves[3]) {
|
||||
// west
|
||||
if (isEndVertical) {
|
||||
pointWest = isEndPair ? [x - 2, y] : [x - 2, y + 1 * signY]
|
||||
} else {
|
||||
pointWest = [x - 3, y]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [pointNorth, pointEast, pointSouth, pointWest]
|
||||
}
|
||||
|
||||
createInteractionsII(side: string, validInteractions: ([number, number] | undefined)[]) {
|
||||
if (validInteractions[0] !== undefined) {
|
||||
this.addInteraction(validInteractions[0][0], validInteractions[0][1], side, 'north')
|
||||
}
|
||||
if (validInteractions[1] !== undefined) {
|
||||
this.addInteraction(validInteractions[1][0], validInteractions[1][1], side, 'east')
|
||||
}
|
||||
if (validInteractions[2] !== undefined) {
|
||||
this.addInteraction(validInteractions[2][0], validInteractions[2][1], side, 'south')
|
||||
}
|
||||
if (validInteractions[3] !== undefined) {
|
||||
this.addInteraction(validInteractions[3][0], validInteractions[3][1], side, 'west')
|
||||
}
|
||||
}
|
||||
|
||||
cleanInteractions() {
|
||||
this.interactionContainer.removeChildren()
|
||||
}
|
||||
|
||||
addInteraction(x: number, y: number, side: string, direction?: string) {
|
||||
const dot = new Dot(this.ticker, this.scale)
|
||||
dot.alpha = 0.5
|
||||
dot.interactive = true
|
||||
dot.on('pointerdown', () => {
|
||||
console.log('direction :>> ', direction)
|
||||
this.emit(`${side}Click`, direction && { direction, x, y })
|
||||
this.cleanInteractions()
|
||||
})
|
||||
dot.on('pointerover', () => {
|
||||
dot.alpha = 1
|
||||
})
|
||||
dot.on('pointerout', () => {
|
||||
dot.alpha = 0.5
|
||||
})
|
||||
dot.setPosition(this.scaleX(x), this.scaleY(y))
|
||||
dot.addTo(this.interactionContainer)
|
||||
}
|
||||
|
||||
nextTileValidMoves(tile: TileDto, side: string): boolean[] {
|
||||
if (tile === undefined || this.freeEnds === undefined) return [false, false, false, false]
|
||||
if (this.count === 0) return [false, true, false, true] // depends on game mode
|
||||
|
||||
const isLeft = side === 'left'
|
||||
const end = isLeft ? this.freeEnds[0] : this.freeEnds[1]
|
||||
const endTile = isLeft ? this.leftTile : this.rightTile
|
||||
const direction = isLeft ? this.leftDirection : this.rightDirection
|
||||
const signX = isLeft ? -1 : 1
|
||||
const signY = direction === 'south' ? 1 : -1
|
||||
const validMove = tile.pips?.includes(end) ?? false
|
||||
const endX = endTile?.x ?? 0
|
||||
const endY = endTile?.y ?? 0
|
||||
const tileHeight = endTile?.height ?? 0
|
||||
const margin = 20
|
||||
const spaceNeeded = tileHeight / 2 + tileHeight + margin
|
||||
const isSecond = this.count === 1
|
||||
const isPair = isTilePair(tile)
|
||||
|
||||
let canPlayNorth = false
|
||||
let canPlaySouth = false
|
||||
let canPlayEast = false
|
||||
let canPlayWest = false
|
||||
|
||||
if (validMove) {
|
||||
canPlayEast = direction !== 'west' && endX + signX * spaceNeeded < this.width
|
||||
canPlayWest = direction !== 'east' && endX + signX * spaceNeeded > 0
|
||||
canPlayNorth = direction !== 'south' && endY + signY * spaceNeeded > 0
|
||||
canPlaySouth = direction !== 'north' && endY + signY * spaceNeeded < this.height
|
||||
}
|
||||
|
||||
if (isSecond) {
|
||||
canPlayNorth = canPlaySouth = false
|
||||
}
|
||||
|
||||
if (isPair && !endTile?.isVertical()) {
|
||||
canPlayNorth = canPlaySouth = false
|
||||
}
|
||||
|
||||
if (isPair && endTile?.isVertical()) {
|
||||
if (direction === 'north') {
|
||||
canPlaySouth = false
|
||||
} else if (direction === 'south') {
|
||||
canPlayNorth = false
|
||||
} else if (direction === 'east') {
|
||||
canPlayWest = false
|
||||
} else if (direction === 'west') {
|
||||
canPlayEast = false
|
||||
}
|
||||
}
|
||||
return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
|
||||
}
|
||||
}
|
||||
|
14
src/utilities/Dot.ts
Normal file
14
src/utilities/Dot.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Graphics, Texture, Ticker } from 'pixi.js'
|
||||
import { SpriteBase } from './SpriteBase'
|
||||
|
||||
export class Dot extends SpriteBase {
|
||||
constructor(ticker: Ticker, scale: number = 1) {
|
||||
super(ticker, scale)
|
||||
this.sprite.texture = this.createTexture()
|
||||
this.anchor = 0.5
|
||||
}
|
||||
|
||||
createTexture(): Texture {
|
||||
return Texture.from('dot')
|
||||
}
|
||||
}
|
136
src/utilities/Game.ts
Normal file
136
src/utilities/Game.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { Application, Assets } from 'pixi.js'
|
||||
import { Board } from './Board'
|
||||
import Hand from './Hand'
|
||||
import { assets } from '@/utilities/assets'
|
||||
import type { Movement, TileDto } from './interfaces'
|
||||
import type { Tile } from './Tile'
|
||||
|
||||
export class Game {
|
||||
public board!: Board
|
||||
public hand!: Hand
|
||||
private app: Application = new Application()
|
||||
private selectedTile: TileDto | undefined
|
||||
|
||||
constructor(
|
||||
private options: { boardScale: number; handScale: number; width: number; height: number } = {
|
||||
boardScale: 1,
|
||||
handScale: 1,
|
||||
width: 1200,
|
||||
height: 650
|
||||
},
|
||||
private emit: any,
|
||||
private props: any
|
||||
) {}
|
||||
|
||||
async setup(): Promise<HTMLCanvasElement> {
|
||||
const width = 1200
|
||||
const height = 650
|
||||
|
||||
await this.app.init({ width, height })
|
||||
return this.app.canvas
|
||||
}
|
||||
|
||||
start() {
|
||||
this.board = new Board(this.app)
|
||||
this.hand = new Hand(this.app)
|
||||
this.hand.scale = this.options.handScale
|
||||
this.board.scale = this.options.boardScale
|
||||
this.setBoardEvents()
|
||||
this.setHandEvents()
|
||||
}
|
||||
|
||||
async preload() {
|
||||
await Assets.load(assets)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.removeBoardEvents()
|
||||
this.removeHandEvents()
|
||||
this.app.destroy()
|
||||
}
|
||||
|
||||
private setHandEvents() {
|
||||
this.hand.on('handUpdated', (tiles: Tile[]) => {
|
||||
this.board.setPlayerHand(tiles)
|
||||
})
|
||||
this.hand.on('tileClick', (tile: TileDto) => {
|
||||
this.selectedTile = tile
|
||||
if (tile !== undefined) {
|
||||
this.board.setMovesForTile(this.getMoves(tile), tile)
|
||||
} else {
|
||||
this.board.cleanInteractions()
|
||||
}
|
||||
})
|
||||
|
||||
this.hand.on('passClick', () => {
|
||||
const move: Movement = {
|
||||
id: '',
|
||||
type: 'pass',
|
||||
playerId: this.props.playerId ?? ''
|
||||
}
|
||||
this.emit('move', move)
|
||||
this.board.updateBoard(move)
|
||||
})
|
||||
}
|
||||
|
||||
getMoves(tile: any): [boolean, boolean] {
|
||||
if (tile === undefined) return [false, false]
|
||||
if (this.board.count === 0) return [false, true]
|
||||
|
||||
const validEnds: [boolean, boolean] = [false, false]
|
||||
const freeEnds = this.board.freeEnds
|
||||
|
||||
if (freeEnds !== undefined) {
|
||||
if (tile.pips != undefined) {
|
||||
validEnds[0] = tile.pips.includes(freeEnds[0])
|
||||
validEnds[1] = tile.pips.includes(freeEnds[1])
|
||||
}
|
||||
}
|
||||
return validEnds
|
||||
}
|
||||
|
||||
public setCanMakeMove(value: boolean) {
|
||||
this.hand.canMove = value
|
||||
this.board.canMove = value
|
||||
}
|
||||
|
||||
private setBoardEvents() {
|
||||
this.board.on('leftClick', (data) => {
|
||||
console.log('left data :>> ', data)
|
||||
if (this.selectedTile === undefined) return
|
||||
const move: Movement = {
|
||||
tile: this.selectedTile,
|
||||
type: 'left',
|
||||
playerId: this.props.playerId ?? '',
|
||||
...data
|
||||
}
|
||||
this.emit('move', move)
|
||||
this.hand.tileMoved(this.selectedTile)
|
||||
this.board.updateBoard({ ...move, tile: this.selectedTile })
|
||||
})
|
||||
|
||||
this.board.on('rightClick', (data) => {
|
||||
console.log('right data :>> ', data)
|
||||
if (this.selectedTile === undefined) return
|
||||
const move: Movement = {
|
||||
tile: this.selectedTile,
|
||||
type: 'right',
|
||||
playerId: this.props.playerId ?? '',
|
||||
...data
|
||||
}
|
||||
this.emit('move', move)
|
||||
this.hand.tileMoved(this.selectedTile)
|
||||
this.board.updateBoard({ ...move, tile: this.selectedTile })
|
||||
})
|
||||
}
|
||||
|
||||
private removeBoardEvents() {
|
||||
this.board.off('leftClick')
|
||||
this.board.off('rightClick')
|
||||
}
|
||||
|
||||
private removeHandEvents() {
|
||||
this.hand.off('tileClick')
|
||||
this.hand.off('passClick')
|
||||
}
|
||||
}
|
@ -3,67 +3,107 @@ import {
|
||||
Container,
|
||||
EventEmitter,
|
||||
Graphics,
|
||||
Rectangle,
|
||||
RoundedRectangle,
|
||||
Sprite,
|
||||
Text,
|
||||
Texture
|
||||
Texture,
|
||||
Ticker
|
||||
} from 'pixi.js'
|
||||
import { Tile } from './Tile'
|
||||
import type { PlayerState } from './interfaces'
|
||||
import type { PlayerState, TileDto } from './interfaces'
|
||||
import { GlowFilter } from 'pixi-filters'
|
||||
|
||||
export default class Hand extends EventEmitter {
|
||||
tiles: Tile[] = []
|
||||
container: Container
|
||||
container: Container = new Container()
|
||||
buttonPassContainer: Container = new Container()
|
||||
height: number
|
||||
width: number
|
||||
ticker: Ticker
|
||||
lastTimeClicked: number = 0
|
||||
doubleClickThreshold: number = 300
|
||||
initialized: boolean = false
|
||||
_canMove: boolean = false
|
||||
scale: number = 1
|
||||
|
||||
constructor(container: Container, canvas: HTMLCanvasElement) {
|
||||
constructor(app: Application) {
|
||||
super()
|
||||
this.container = container
|
||||
app.stage.addChild(this.container)
|
||||
this.ticker = app.ticker
|
||||
this.height = 130
|
||||
this.width = canvas.width
|
||||
this.container.y = canvas.height - this.height
|
||||
this.width = app.canvas.width
|
||||
this.container.y = app.canvas.height - this.height
|
||||
this.container.width = this.width
|
||||
this.container.height = this.height
|
||||
this.addBg()
|
||||
this.createPassButton()
|
||||
}
|
||||
|
||||
setTiles(playerState: PlayerState) {
|
||||
get canMove() {
|
||||
return this._canMove
|
||||
}
|
||||
|
||||
set canMove(value: boolean) {
|
||||
this._canMove = value
|
||||
this.buttonPassContainer.eventMode = value ? 'static' : 'none'
|
||||
this.buttonPassContainer.cursor = value ? 'pointer' : 'default'
|
||||
this.tiles.forEach((tile) => {
|
||||
tile.interactive = value
|
||||
})
|
||||
}
|
||||
|
||||
initialize(playerState: PlayerState) {
|
||||
this.tiles = this.createTiles(playerState)
|
||||
this.emit('handUpdated', this.tiles)
|
||||
this.initialized = this.tiles.length > 0
|
||||
this.renderTiles()
|
||||
}
|
||||
|
||||
private addBg() {
|
||||
console.log('addBg')
|
||||
const bg = new Sprite(Texture.WHITE)
|
||||
bg.alpha = 0.05
|
||||
bg.alpha = 0.08
|
||||
bg.width = this.width
|
||||
bg.height = this.height
|
||||
this.container.addChild(bg)
|
||||
this.container.addChild(
|
||||
new Text({
|
||||
text: 'Hand',
|
||||
style: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
fill: 0xff1010,
|
||||
align: 'center'
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private onClick(tile: Tile) {
|
||||
const selected = this.tiles.find((t) => t.selectd)
|
||||
private onTileClick(tile: Tile) {
|
||||
// if (Date.now() - this.lastTimeClicked < this.doubleClickThreshold) {
|
||||
// this.emit('tileDoubleClick', { id: tile.id })
|
||||
// return
|
||||
// }
|
||||
|
||||
const selected = this.tiles.find((t) => t.selected)
|
||||
if (selected) {
|
||||
selected.y = selected.y + 10
|
||||
selected.selectd = false
|
||||
this.deselectTile(selected)
|
||||
if (selected.id === tile.id) {
|
||||
this.emit('tileClick')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tile.selectd = true
|
||||
tile.y = tile.y - 10
|
||||
this.emit('tileClick', { id: tile.id })
|
||||
tile.selected = true
|
||||
tile.alpha = 1
|
||||
|
||||
tile.animateTo(tile.x, tile.y - 10)
|
||||
this.emit('tileClick', tile.toPlain())
|
||||
}
|
||||
|
||||
private deselectTile(selected: Tile) {
|
||||
selected.animateTo(selected.x, selected.y + 10)
|
||||
selected.selected = false
|
||||
selected.alpha = 0.7
|
||||
}
|
||||
|
||||
public tileMoved(tileDto: TileDto) {
|
||||
const tile = this.tiles.find((t) => t.id === tileDto.id)
|
||||
|
||||
if (!tile) return
|
||||
|
||||
tile.interactive = false
|
||||
tile.clearFilters()
|
||||
tile.off('pointerdown')
|
||||
tile.off('pointerover')
|
||||
tile.off('pointerout')
|
||||
}
|
||||
|
||||
private createPassButton() {
|
||||
@ -81,51 +121,85 @@ export default class Hand extends EventEmitter {
|
||||
})
|
||||
text.anchor = 0.5
|
||||
|
||||
const buttonPassContainer = new Container()
|
||||
this.buttonPassContainer = new Container()
|
||||
|
||||
buttonPassContainer.addChild(rectangle)
|
||||
buttonPassContainer.addChild(text)
|
||||
this.buttonPassContainer.addChild(rectangle)
|
||||
this.buttonPassContainer.addChild(text)
|
||||
|
||||
text.y = buttonPassContainer.height / 2 - 4
|
||||
text.x = buttonPassContainer.width / 2 - 8
|
||||
text.y = this.buttonPassContainer.height / 2 - 4
|
||||
text.x = this.buttonPassContainer.width / 2 - 8
|
||||
|
||||
buttonPassContainer.eventMode = 'static'
|
||||
buttonPassContainer.cursor = 'pointer'
|
||||
buttonPassContainer.x = 20
|
||||
buttonPassContainer.y = this.height / 2 - 10
|
||||
this.buttonPassContainer.eventMode = 'none'
|
||||
this.buttonPassContainer.cursor = 'default'
|
||||
this.buttonPassContainer.x = 20
|
||||
this.buttonPassContainer.y = this.height / 2 - 10
|
||||
rectangle.alpha = 0.7
|
||||
text.alpha = 0.7
|
||||
buttonPassContainer.on('pointerdown', () => {
|
||||
this.buttonPassContainer.on('pointerdown', () => {
|
||||
this.emit('passClick')
|
||||
})
|
||||
buttonPassContainer.on('pointerover', () => {
|
||||
this.buttonPassContainer.on('pointerover', () => {
|
||||
rectangle.alpha = 1
|
||||
text.alpha = 1
|
||||
})
|
||||
|
||||
buttonPassContainer.on('pointerout', () => {
|
||||
this.buttonPassContainer.on('pointerout', () => {
|
||||
rectangle.alpha = 0.7
|
||||
text.alpha = 0.7
|
||||
})
|
||||
|
||||
this.container.addChild(buttonPassContainer)
|
||||
this.container.addChild(this.buttonPassContainer)
|
||||
}
|
||||
|
||||
update(playerState: PlayerState) {
|
||||
if (!this.initialized) {
|
||||
this.initialize(playerState)
|
||||
return
|
||||
}
|
||||
const missing: Tile | undefined = this.tiles.find(
|
||||
(tile: Tile) => !playerState.hand.find((t) => t.id === tile.id)
|
||||
)
|
||||
if (missing) {
|
||||
this.container.removeChild(missing.getSprite())
|
||||
this.tiles = this.tiles.filter((tile) => tile.id !== missing.id)
|
||||
this.emit('handUpdated', this.tiles)
|
||||
}
|
||||
this.renderTiles()
|
||||
}
|
||||
|
||||
private createTiles(playerState: PlayerState) {
|
||||
const deltaX = (playerState.hand.length * 50 - 5) / 2
|
||||
return playerState.hand.map((tile, i) => {
|
||||
const newTile: Tile = new Tile(tile.id, tile.pips)
|
||||
|
||||
newTile.y = newTile.height / 2 + 20
|
||||
newTile.x = deltaX + newTile.width / 2 + i * (newTile.width + 5)
|
||||
newTile.eventMode = 'static'
|
||||
newTile.cursor = 'pointer'
|
||||
const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale)
|
||||
newTile.alpha = 0.7
|
||||
this.container.addChild(newTile)
|
||||
newTile.on('pointerdown', () => this.onClick(newTile))
|
||||
newTile.on('pointerover', () => (newTile.alpha = 1))
|
||||
newTile.on('pointerout', () => (newTile.alpha = 0.7))
|
||||
newTile.anchor = 0.5
|
||||
newTile.addTo(this.container)
|
||||
newTile.on('pointerdown', () => this.onTileClick(newTile))
|
||||
newTile.on('pointerover', () => {
|
||||
newTile.alpha = 1
|
||||
newTile.setFilters([
|
||||
new GlowFilter({
|
||||
distance: 10,
|
||||
outerStrength: 2,
|
||||
innerStrength: 1,
|
||||
color: 0xffffff,
|
||||
quality: 0.5
|
||||
})
|
||||
])
|
||||
})
|
||||
newTile.on('pointerout', () => {
|
||||
if (!newTile.selected) {
|
||||
newTile.alpha = 0.7
|
||||
newTile.getSprite().filters = []
|
||||
}
|
||||
})
|
||||
return newTile
|
||||
})
|
||||
}
|
||||
|
||||
renderTiles() {
|
||||
const deltaX = this.width / 2 - (this.tiles.length * 50 - 5) / 2
|
||||
this.tiles.forEach((tile, i) => {
|
||||
tile.setPosition(deltaX + tile.width / 2 + i * (tile.width + 5), tile.height / 2 + 20)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
111
src/utilities/SpriteBase.ts
Normal file
111
src/utilities/SpriteBase.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { Sprite, Texture, Ticker } from 'pixi.js'
|
||||
|
||||
export abstract class SpriteBase {
|
||||
private _interactive: boolean = false
|
||||
protected sprite: Sprite = new Sprite()
|
||||
|
||||
constructor(
|
||||
protected ticker?: Ticker,
|
||||
protected scale: number = 1
|
||||
) {
|
||||
this.ticker = ticker
|
||||
this.scale = scale
|
||||
}
|
||||
|
||||
abstract createTexture(): Texture
|
||||
|
||||
set interactive(value: boolean) {
|
||||
this._interactive = value
|
||||
this.sprite.eventMode = value ? 'static' : 'none'
|
||||
this.sprite.cursor = value ? 'pointer' : 'default'
|
||||
}
|
||||
|
||||
get interactive(): boolean {
|
||||
return this._interactive
|
||||
}
|
||||
|
||||
get x(): number {
|
||||
return this.sprite.x
|
||||
}
|
||||
|
||||
get y(): number {
|
||||
return this.sprite.y
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this.sprite.width
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this.sprite.height
|
||||
}
|
||||
|
||||
set alpha(value: number) {
|
||||
this.sprite.alpha = value
|
||||
}
|
||||
|
||||
get alpha(): number {
|
||||
return this.sprite.alpha
|
||||
}
|
||||
|
||||
set anchor(value: { x: number; y: number } | number) {
|
||||
if (typeof value === 'number') {
|
||||
this.sprite.anchor.set(value)
|
||||
return
|
||||
}
|
||||
this.sprite.anchor.set(value.x, value.y)
|
||||
}
|
||||
|
||||
on(event: string, fn: any): void {
|
||||
this.sprite.on(event, fn)
|
||||
}
|
||||
|
||||
off(event: string): void {
|
||||
this.sprite.off(event)
|
||||
}
|
||||
|
||||
getSprite(): Sprite {
|
||||
return this.sprite
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number) {
|
||||
this.sprite.x = x
|
||||
this.sprite.y = y
|
||||
}
|
||||
|
||||
setFilters(filters: any[]) {
|
||||
this.sprite.filters = filters
|
||||
}
|
||||
|
||||
clearFilters() {
|
||||
this.sprite.filters = []
|
||||
}
|
||||
|
||||
animateTo(x: number, y: number) {
|
||||
const initialX = this.sprite.x
|
||||
const initialY = this.sprite.y
|
||||
|
||||
const deltaX = x - this.sprite.x
|
||||
const deltaY = y - this.sprite.y
|
||||
let elapsed: number = 0
|
||||
const duration: number = 10
|
||||
|
||||
const tick: any = (delta: any) => {
|
||||
elapsed += delta.deltaTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
|
||||
this.sprite.x = initialX + deltaX * progress
|
||||
this.sprite.y = initialY + deltaY * progress
|
||||
|
||||
if (progress === 1) {
|
||||
this.ticker?.remove(tick)
|
||||
}
|
||||
}
|
||||
|
||||
this.ticker?.add(tick)
|
||||
}
|
||||
|
||||
addTo(container: any) {
|
||||
container.addChild(this.sprite)
|
||||
}
|
||||
}
|
@ -1,25 +1,86 @@
|
||||
import { Sprite, Texture } from 'pixi.js'
|
||||
import { Texture, Ticker } from 'pixi.js'
|
||||
import { SpriteBase } from './SpriteBase'
|
||||
|
||||
export class Tile extends Sprite {
|
||||
id: string
|
||||
pips: [number, number] | undefined
|
||||
selectd: boolean = false
|
||||
export class Tile extends SpriteBase {
|
||||
selected: boolean = false
|
||||
orientation: string
|
||||
// sprite: Sprite = new Sprite()
|
||||
// private _interactive: boolean = false
|
||||
|
||||
constructor(id: string, pips?: [number, number]) {
|
||||
super()
|
||||
constructor(
|
||||
public id: string,
|
||||
ticker?: Ticker,
|
||||
public pips?: [number, number],
|
||||
scale: number = 1
|
||||
) {
|
||||
super(ticker, scale)
|
||||
this.id = id
|
||||
this.anchor.set(0.5)
|
||||
this.width = 50
|
||||
this.height = 100
|
||||
// this.ticker = ticker
|
||||
this.sprite.texture = this.createTexture()
|
||||
this.sprite.anchor.set(0.5)
|
||||
this.sprite.height = 100 * this.scale
|
||||
this.sprite.width = 50 * this.scale
|
||||
this.pips = pips
|
||||
this.texture = this.getTexture()
|
||||
this.orientation = 'north'
|
||||
}
|
||||
|
||||
private getTexture(): Texture {
|
||||
toPlain() {
|
||||
return {
|
||||
id: this.id,
|
||||
pips: this.pips,
|
||||
orientation: this.orientation,
|
||||
x: this.sprite.x,
|
||||
y: this.sprite.y,
|
||||
width: this.sprite.width,
|
||||
height: this.sprite.height
|
||||
}
|
||||
}
|
||||
|
||||
equals(tile: Tile | undefined): boolean {
|
||||
if (this.pips === undefined || tile === undefined || tile.pips === undefined) return false
|
||||
return (
|
||||
(this.pips[0] === tile.pips[0] && this.pips[1] === tile.pips[1]) ||
|
||||
(this.pips[0] === tile.pips[1] && this.pips[1] === tile.pips[0])
|
||||
)
|
||||
}
|
||||
|
||||
createTexture(): Texture {
|
||||
if (this.pips !== undefined) {
|
||||
return Texture.from(`tile-${this.pips[1]}_${this.pips[0]}`)
|
||||
const sortedInversrPips = this.pips.slice().sort((a, b) => b - a)
|
||||
return Texture.from(`tile-${sortedInversrPips[0]}_${sortedInversrPips[1]}`)
|
||||
} else {
|
||||
return Texture.from('tile-back')
|
||||
}
|
||||
}
|
||||
|
||||
reScale(scale: number) {
|
||||
this.scale = scale
|
||||
this.sprite.height = 100 * this.scale
|
||||
this.sprite.width = 50 * this.scale
|
||||
}
|
||||
|
||||
isPair(): boolean {
|
||||
return this.pips !== undefined && this.pips[0] === this.pips[1]
|
||||
}
|
||||
isVertical(): boolean {
|
||||
return this.orientation === 'north' || this.orientation === 'south'
|
||||
}
|
||||
|
||||
setOrientation(value: string) {
|
||||
switch (value) {
|
||||
case 'north':
|
||||
this.sprite.rotation = 0
|
||||
break
|
||||
case 'east':
|
||||
this.sprite.rotation = Math.PI / 2
|
||||
break
|
||||
case 'south':
|
||||
this.sprite.rotation = Math.PI
|
||||
break
|
||||
case 'west':
|
||||
this.sprite.rotation = (3 * Math.PI) / 2
|
||||
break
|
||||
}
|
||||
this.orientation = value
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import tile6_3 from '@/assets/images/tiles/6-3.png'
|
||||
import tile6_4 from '@/assets/images/tiles/6-4.png'
|
||||
import tile6_5 from '@/assets/images/tiles/6-5.png'
|
||||
import tile6_6 from '@/assets/images/tiles/6-6.png'
|
||||
import dot from '@/assets/images/circle.png'
|
||||
|
||||
export const assets = [
|
||||
{ alias: 'tile-back', src: tileBack },
|
||||
@ -57,5 +58,6 @@ export const assets = [
|
||||
{ alias: 'tile-6_3', src: tile6_3 },
|
||||
{ alias: 'tile-6_4', src: tile6_4 },
|
||||
{ alias: 'tile-6_5', src: tile6_5 },
|
||||
{ alias: 'tile-6_6', src: tile6_6 }
|
||||
{ alias: 'tile-6_6', src: tile6_6 },
|
||||
{ alias: 'dot', src: dot }
|
||||
]
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { Graphics, Container } from 'pixi.js'
|
||||
|
||||
export function getColorBackground(container: Container, colorName: string, alpha: number = 0.5) {
|
||||
const graphics = new Graphics()
|
||||
const color = 0xffffff
|
||||
graphics.rect(0, 0, container.width, container.height)
|
||||
graphics.fill(color)
|
||||
graphics.alpha = alpha
|
||||
graphics.x = 0
|
||||
graphics.y = 0
|
||||
console.log('graphics :>> ', graphics)
|
||||
|
||||
return graphics
|
||||
}
|
25
src/utilities/fonts.ts
Normal file
25
src/utilities/fonts.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Text, TextStyle } from 'pixi.js'
|
||||
|
||||
export const dropShadowStyle = {
|
||||
alpha: 0.5,
|
||||
angle: 0.3,
|
||||
blur: 5,
|
||||
distance: 4
|
||||
}
|
||||
|
||||
export const mainText = new TextStyle({
|
||||
dropShadow: dropShadowStyle,
|
||||
fill: '#b71a1a',
|
||||
fontFamily: 'Arial, Helvetica, sans-serif',
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 1,
|
||||
stroke: '#658f56'
|
||||
})
|
||||
|
||||
export function createText(str: string, x: number, y: number, style: TextStyle = mainText) {
|
||||
const text = new Text({ text: str, style })
|
||||
text.anchor.set(0.5, 0.5)
|
||||
text.x = x
|
||||
text.y = y
|
||||
return text
|
||||
}
|
62
src/utilities/helpers.ts
Normal file
62
src/utilities/helpers.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Graphics, Container } from 'pixi.js'
|
||||
import type { TileDto } from './interfaces'
|
||||
|
||||
export function getColorBackground(container: Container, colorName: string, alpha: number = 0.5) {
|
||||
const graphics = new Graphics()
|
||||
const color = 0xffffff
|
||||
graphics.rect(0, 0, container.width, container.height)
|
||||
graphics.fill(color)
|
||||
graphics.alpha = alpha
|
||||
graphics.x = 0
|
||||
graphics.y = 0
|
||||
return graphics
|
||||
}
|
||||
|
||||
interface ContainerOptions {
|
||||
width?: number
|
||||
height?: number
|
||||
x?: number
|
||||
y?: number
|
||||
color?: number
|
||||
visible?: boolean
|
||||
parent?: Container
|
||||
}
|
||||
|
||||
const defaultContainerOptions = {
|
||||
width: 100,
|
||||
height: 100,
|
||||
x: 0,
|
||||
y: 0,
|
||||
visible: true
|
||||
}
|
||||
|
||||
export function createContainer(options: ContainerOptions) {
|
||||
const opts = { ...defaultContainerOptions, ...options }
|
||||
const container = new Container()
|
||||
|
||||
const rect = new Graphics().rect(opts.x, opts.y, opts.width, opts.height)
|
||||
|
||||
if (opts.color) {
|
||||
rect.fill(opts.color)
|
||||
}
|
||||
rect.visible = opts.visible
|
||||
container.addChild(rect)
|
||||
if (opts.parent) {
|
||||
opts.parent.addChild(container)
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
export function isTileVertical(tile: TileDto): boolean {
|
||||
return tile.orientation === 'north' || tile.orientation === 'south'
|
||||
}
|
@ -1,20 +1,65 @@
|
||||
export interface PlayerDto {
|
||||
id: string
|
||||
name: string
|
||||
score?: number
|
||||
hand?: string[]
|
||||
}
|
||||
|
||||
export interface TileDto {
|
||||
id: string
|
||||
pips?: [number, number]
|
||||
orientation?: string
|
||||
x?: number
|
||||
y?: number
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
export interface GameSessionState {
|
||||
id: string
|
||||
name: string
|
||||
creator: string
|
||||
players: PlayerDto[]
|
||||
seed: string
|
||||
waitingForPlayers: boolean
|
||||
mode: string
|
||||
pointsToWin: number
|
||||
sessionInProgress: boolean
|
||||
status: string
|
||||
maxPlayers: number
|
||||
numPlayers: number
|
||||
waitingSeconds: number
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
players: any[]
|
||||
boneyard: any[]
|
||||
currentPlayer: string
|
||||
board: any[]
|
||||
id: string
|
||||
players: PlayerDto[]
|
||||
tilesInBoneyard: TileDto[]
|
||||
currentPlayer: PlayerDto | null
|
||||
tilesInBoard: TileDto[]
|
||||
gameInProgress: boolean
|
||||
winner?: any
|
||||
gameBlocked: boolean
|
||||
gameTied: boolean
|
||||
gameId: string
|
||||
tileSelectionPhase: boolean
|
||||
boardFreeEnds: number[]
|
||||
lastMove: Movement
|
||||
}
|
||||
|
||||
export interface PlayerState {
|
||||
id: string
|
||||
name: string
|
||||
score: number
|
||||
hand: any[]
|
||||
hand: TileDto[]
|
||||
teamedWith: string | undefined
|
||||
}
|
||||
|
||||
export interface Movement {
|
||||
id: string
|
||||
type: string
|
||||
tile?: TileDto
|
||||
playerId: string
|
||||
direction?: string
|
||||
x?: number
|
||||
y?: number
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
import { Tile } from './Tile'
|
||||
import type { GameState, Movement } from './interfaces'
|
||||
import type { Game } from './Game'
|
||||
import { wait } from './helpers'
|
||||
|
||||
export const playerState = {
|
||||
id: '6fddcf4f-eaa9-4c87-a599-2af944477091',
|
||||
name: 'arhuako',
|
||||
@ -15,7 +20,7 @@ export const playerState = {
|
||||
},
|
||||
{
|
||||
id: 'f516ef48-99f2-4add-96eb-877f03bf0cb1',
|
||||
pips: [2, 3],
|
||||
pips: [6, 6],
|
||||
flipped: false
|
||||
},
|
||||
{
|
||||
@ -40,3 +45,111 @@ export const playerState = {
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const gameState_0: GameState = {
|
||||
id: 'f043051e-6850-444f-857c-b889220fc187',
|
||||
lastMove: {
|
||||
tile: {
|
||||
id: 'c3f6b525-2e6f-455a-b355-9b8c8f5075f0',
|
||||
pips: [6, 6]
|
||||
},
|
||||
type: 'both',
|
||||
playerId: '137b68a6-f26c-43f5-badf-e5ae406fd059',
|
||||
id: 'c71943a5-1fef-4418-bd66-39767ac36817'
|
||||
},
|
||||
gameInProgress: true,
|
||||
winner: null,
|
||||
tileSelectionPhase: false,
|
||||
gameBlocked: false,
|
||||
gameTied: false,
|
||||
gameId: 'd76c4f98-1f26-4390-9e8d-2d727876b710',
|
||||
tilesInBoneyard: [],
|
||||
players: [
|
||||
{
|
||||
id: 'f639f263-e415-428c-bdd2-113f00f3c392',
|
||||
name: 'arhuako',
|
||||
score: 0,
|
||||
hand: [
|
||||
'51399f92-7555-4ffc-91ed-f6b5b9041a54',
|
||||
'e9c40c13-c821-4bad-810c-12e7cb79b47d',
|
||||
'95e8412b-1938-48d5-9647-cb8617659db0',
|
||||
'54948d0a-b7a4-416c-a3f7-6602cfff1851',
|
||||
'a40e6a76-c2e1-4692-82b3-603a4412a6a9',
|
||||
'cb7827df-fe19-4c28-a786-a47e07c2d368',
|
||||
'6d5a31d3-b734-4312-b8fd-1b5efa3fc258'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '77b2c512-d9e5-45f3-850f-99baab1befdf',
|
||||
name: 'Alice (AI)',
|
||||
score: 0,
|
||||
hand: [
|
||||
'066fd011-cabe-483b-a985-c649f4908356',
|
||||
'01434825-eb3c-4baf-8ad1-cc055f982e75',
|
||||
'c085c90f-f69c-4f83-acb6-87c576927366',
|
||||
'1b5d5b46-a41c-4e69-9a02-efff9adbc201',
|
||||
'74833dcc-1526-4c6c-a64c-a7807e647e77',
|
||||
'fb7e6976-67a9-496f-9758-0b172aa069dd',
|
||||
'58b612f0-cf40-4b5f-ba05-8a089f19b43a'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '137b68a6-f26c-43f5-badf-e5ae406fd059',
|
||||
name: 'Bob (AI)',
|
||||
score: 0,
|
||||
hand: [
|
||||
'9d5dcb97-7478-4030-a0f6-4e7080a5b8e0',
|
||||
'a07e1aaf-e651-4678-a61c-414f4324d8e8',
|
||||
'4db7aebb-ab4b-4709-b208-b39176e8d70a',
|
||||
'd1733f9c-86a6-4597-b1ff-ec44e9a375d1',
|
||||
'cf6e2c1c-57f8-4342-8ff4-8046971f99db',
|
||||
'33cec060-7f40-4af9-9182-f98cbacbae10'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '188c1d69-8c5c-4fec-a08b-dc5fa09d49fd',
|
||||
name: 'Charlie (AI)',
|
||||
score: 0,
|
||||
hand: [
|
||||
'58433be6-e8d9-4ed2-bbdc-4328425a3687',
|
||||
'3e24a6cd-02ea-436a-a3e4-41ae62c717d0',
|
||||
'2a2723d1-6c46-4718-a1c2-fa8d2c148ac8',
|
||||
'd521de5e-1db1-4a2e-971d-f91cc761342c',
|
||||
'13d943d7-b694-4419-82f5-729117878d01',
|
||||
'afcf2509-acfb-47ed-ade7-d08f1a20afd2',
|
||||
'f50f3589-41ab-4d99-919e-c4c7a71ef5b0'
|
||||
]
|
||||
}
|
||||
],
|
||||
currentPlayer: {
|
||||
id: '188c1d69-8c5c-4fec-a08b-dc5fa09d49fd',
|
||||
name: 'arhuako'
|
||||
},
|
||||
tilesInBoard: [
|
||||
{
|
||||
id: 'c3f6b525-2e6f-455a-b355-9b8c8f5075f0',
|
||||
pips: [6, 6]
|
||||
}
|
||||
],
|
||||
boardFreeEnds: [6, 6]
|
||||
}
|
||||
|
||||
export function getMovement(
|
||||
pips: [number, number] = [6, 6],
|
||||
side: string = 'left',
|
||||
scale: number = 1
|
||||
): Movement {
|
||||
const tile = new Tile('string', undefined, pips, scale)
|
||||
tile.orientation = 'north'
|
||||
return {
|
||||
id: 'movemnetid',
|
||||
type: side,
|
||||
tile,
|
||||
playerId: 'string'
|
||||
}
|
||||
}
|
||||
|
||||
export async function mockMove(game: Game, pips: [number, number], side: string = 'left') {
|
||||
await wait(1000)
|
||||
game.board.updateBoard(getMovement(pips, side, game.board.scale))
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
export type ScaleFunction = Function & {
|
||||
inverse: Function
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param domain example: [0, 100]
|
||||
* @param range examaple: [0, width]
|
||||
*/
|
||||
export function Scale(domain: number[], range: number[]): Function {
|
||||
return function (value: number): number {
|
||||
export function Scale(domain: number[], range: number[]): ScaleFunction {
|
||||
function Fn(value: number): number {
|
||||
return ((value - domain[0]) * (range[1] - range[0])) / (domain[1] - domain[0]) + range[0]
|
||||
}
|
||||
|
||||
Fn.inverse = function (value: number): number {
|
||||
return ((value - range[0]) * (domain[1] - domain[0])) / (range[1] - range[0]) + domain[0]
|
||||
}
|
||||
|
||||
return Fn
|
||||
}
|
||||
|
@ -1,119 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import GameComponent from '@/components/GameComponent.vue'
|
||||
import TileComponent from '@/components/TileComponent.vue'
|
||||
import type { GameState, PlayerState } from '@/utilities/interfaces'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { inject, onBeforeUnmount, ref } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
|
||||
const socketService: any = inject('socket')
|
||||
const { socket } = socketService
|
||||
let gameState = ref<GameState | undefined>(undefined)
|
||||
let playerState = ref<PlayerState | undefined>(undefined)
|
||||
let data = ref('')
|
||||
let responseField = ref('')
|
||||
let statusField = ref('')
|
||||
let sessionId = ref('')
|
||||
let seed = ref('')
|
||||
let playerId = ref('')
|
||||
let selectdAction: any = undefined
|
||||
let canSelectTile = false
|
||||
let canMakeMove = false
|
||||
let tileSelected = ''
|
||||
let moveType = ''
|
||||
|
||||
const { toClipboard } = useClipboard()
|
||||
const gameStore = useGameStore()
|
||||
const { moveToMake, canMakeMove, sessionState, gameState } = storeToRefs(gameStore)
|
||||
const options = [
|
||||
{ value: 'createSession', default: '{"user": "arhuako"}' },
|
||||
{ value: 'startSession', default: (id: string) => `{"sessionId": "${id}"}` },
|
||||
{ value: 'joinSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||||
{ value: 'leaveSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||||
{ value: 'chat message', default: 'chat message' }
|
||||
{ value: 'startSession', default: (id: string) => `{"sessionId": "${id}"}` }
|
||||
// { value: 'joinSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||||
// { value: 'leaveSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||||
// { value: 'chat message', default: 'chat message' }
|
||||
]
|
||||
|
||||
onMounted(async () => {
|
||||
socket.on('gameState', (data: GameState, callback: any) => {
|
||||
gameState.value = data
|
||||
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||||
callback({
|
||||
status: 'ok'
|
||||
})
|
||||
})
|
||||
onMounted(async () => {})
|
||||
|
||||
socket.on('playerState', (data: PlayerState, callback: any) => {
|
||||
console.log('playerState :>> ', data)
|
||||
playerState.value = data
|
||||
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||||
callback({
|
||||
status: 'ok'
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('makeMove', async (data: any, callback: any) => {
|
||||
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||||
canMakeMove = true
|
||||
while (canMakeMove) {
|
||||
await wait(500)
|
||||
}
|
||||
callback({
|
||||
status: 'ok',
|
||||
tileId: tileSelected,
|
||||
moveType
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('chooseTile', async (data: any, callback: any) => {
|
||||
statusField.value = statusField.value + JSON.stringify(data, null, 2)
|
||||
canSelectTile = true
|
||||
while (canSelectTile) {
|
||||
await wait(500)
|
||||
}
|
||||
callback({
|
||||
status: 'ok',
|
||||
tileId: tileSelected
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function selectTile(id: string) {
|
||||
if (canSelectTile) {
|
||||
tileSelected = id
|
||||
canSelectTile = false
|
||||
}
|
||||
}
|
||||
|
||||
function selectTileToMove(id: string) {
|
||||
if (canMakeMove) {
|
||||
if (tileSelected === id) {
|
||||
tileSelected = ''
|
||||
return
|
||||
}
|
||||
tileSelected = id
|
||||
}
|
||||
}
|
||||
|
||||
function onTileClick(tileId: string) {
|
||||
console.log('tileId :>> ', tileId)
|
||||
selectTile(tileId)
|
||||
}
|
||||
|
||||
function makeMove(type: string) {
|
||||
if (canMakeMove) {
|
||||
if (type === 'pass') {
|
||||
tileSelected = ''
|
||||
canMakeMove = false
|
||||
} else if (tileSelected) {
|
||||
moveType = type
|
||||
canMakeMove = false
|
||||
}
|
||||
}
|
||||
function makeMove(move: any) {
|
||||
moveToMake.value = move
|
||||
canMakeMove.value = false
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
socketService.disconnect()
|
||||
// socketService.disconnect()
|
||||
})
|
||||
|
||||
function actionSelected() {
|
||||
console.log('event :>> ', selectdAction)
|
||||
|
||||
if (selectdAction.value === 'createSession') {
|
||||
responseField.value = ''
|
||||
} else if (selectdAction.value === 'startSession') {
|
||||
@ -129,15 +53,36 @@ const getMessage = (msg: string) => {
|
||||
return msg
|
||||
}
|
||||
|
||||
async function createSession() {
|
||||
const response = await socketService.sendMessageWithAck('createSession', { user: 'arhuako' })
|
||||
sessionId.value = response.sessionId
|
||||
playerId.value = response.playerId
|
||||
}
|
||||
|
||||
async function startSession() {
|
||||
if (sessionId.value) {
|
||||
await socketService.sendMessageWithAck('startSession', {
|
||||
sessionId: sessionId.value,
|
||||
seed: seed.value.trim()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function joinSession() {
|
||||
if (sessionId.value) {
|
||||
await socketService.sendMessageWithAck('joinSession', {
|
||||
user: 'pepe',
|
||||
sessionId: sessionId.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
if (selectdAction && data.value.trim() !== '') {
|
||||
console.log('socketService :>> ', socketService)
|
||||
|
||||
const response = await socketService.sendMessageWithAck(
|
||||
selectdAction.value,
|
||||
getMessage(data.value.trim())
|
||||
)
|
||||
console.log('response :>> ', response)
|
||||
handleResponse(response)
|
||||
}
|
||||
// socketService.emit('message', data.value)
|
||||
@ -146,6 +91,7 @@ async function sendMessage() {
|
||||
function handleResponse(response: any) {
|
||||
if (selectdAction.value === 'createSession') {
|
||||
sessionId.value = response.sessionId
|
||||
playerId.value = response.playerId
|
||||
}
|
||||
|
||||
data.value = ''
|
||||
@ -155,101 +101,113 @@ function handleResponse(response: any) {
|
||||
: responseField.value + '\n---\n ' + responseStr
|
||||
selectdAction = undefined
|
||||
}
|
||||
|
||||
function copySeed() {
|
||||
if (sessionState?.value?.seed) toClipboard(sessionState.value.seed)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<GameComponent :gameState="gameState" :playerState="playerState" @tileClick="onTileClick" />
|
||||
<p>SessionID: {{ sessionId }}</p>
|
||||
<ul id="messages"></ul>
|
||||
<form id="form" action="">
|
||||
<p>
|
||||
<select v-model="selectdAction" id="event" autocomplete="off" @change="actionSelected">
|
||||
<option value="">Select event</option>
|
||||
<option :key="option.value" v-for="option in options" :value="option">
|
||||
{{ option.value }}
|
||||
</option>
|
||||
</select>
|
||||
</p>
|
||||
<!-- <p><input id="room" autocomplete="off" /></p> -->
|
||||
<p>
|
||||
<textarea v-model="data" id="message" autocomplete="off" placeholder="Data"></textarea>
|
||||
</p>
|
||||
<p><button @click.prevent.stop="sendMessage">Send</button></p>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<textarea
|
||||
:value="responseField"
|
||||
id="response"
|
||||
autocomplete="off"
|
||||
placeholder="Response"
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<textarea
|
||||
:value="statusField"
|
||||
id="status"
|
||||
autocomplete="off"
|
||||
placeholder="Game status"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<!-- <div style="display: flex; gap: 4px">
|
||||
<TileComponent :revealed="false" :top="6" :bottom="0" />
|
||||
<TileComponent :revealed="true" :top="6" :bottom="0" />
|
||||
</div> -->
|
||||
<h4>Board</h4>
|
||||
<!-- Board -->
|
||||
<div class="board-container">
|
||||
<div v-if="gameState?.tileSelectionPhase">
|
||||
<div class="tiles-container">
|
||||
<TileComponent
|
||||
:key="tile.id"
|
||||
v-for="tile in gameState.boneyard"
|
||||
@tile-clicked="selectTile(tile.id)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="canSelectTile">Please select a tile.</div>
|
||||
<div v-else>Wait your turn.</div>
|
||||
</div>
|
||||
<div v-if="!gameState?.tileSelectionPhase">
|
||||
<div class="tiles-container">
|
||||
<TileComponent
|
||||
:class="{ 'selected-tile': tileSelected === tile.id }"
|
||||
:revealed="true"
|
||||
:key="tile.id"
|
||||
v-for="tile in gameState?.board"
|
||||
:top="tile.pips[0]"
|
||||
:bottom="tile.pips[1]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<section class="block">
|
||||
<p>
|
||||
Running: {{ sessionState?.sessionInProgress }} Seed: {{ sessionState?.seed }}
|
||||
<button @click="copySeed">Copy!</button>
|
||||
</p>
|
||||
<p>FreeEnds: {{ gameState?.boardFreeEnds }} - {{ gameState?.currentPlayer?.name }}</p>
|
||||
<p v-if="sessionId">SessionID: {{ sessionId }} PlayerID: {{ playerId }}</p>
|
||||
</section>
|
||||
<section class="block">
|
||||
<div class="game-container">
|
||||
<GameComponent :playerId="playerId" :canMakeMove="canMakeMove" @move="makeMove" />
|
||||
</div>
|
||||
<!-- End board-->
|
||||
</section>
|
||||
|
||||
<div class="hand-container">
|
||||
<h4>
|
||||
Your Hand <button class="control" @click="makeMove('left')">Left</button
|
||||
><button class="control" @click="makeMove('right')">Right</button
|
||||
><button class="control" @click="makeMove('pass')">Pass</button>
|
||||
</h4>
|
||||
<div class="tiles-container">
|
||||
<TileComponent
|
||||
:revealed="true"
|
||||
:key="tile.id"
|
||||
v-for="tile in playerState?.hand ?? []"
|
||||
@tile-clicked="selectTileToMove(tile.id)"
|
||||
:top="tile.pips[0]"
|
||||
:bottom="tile.pips[1]"
|
||||
/>
|
||||
<section class="block">
|
||||
<div class="fixed-grid has-8-cols">
|
||||
<div class="grid" v-if="!sessionId">
|
||||
<div class="cell">
|
||||
<button style="width: 200px" class="button" @click="createSession">
|
||||
Create Session
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell is-col-span7"></div>
|
||||
</div>
|
||||
<div class="grid" v-if="sessionId">
|
||||
<div class="cell">
|
||||
<button class="button" style="width: 200px" @click="startSession">Start Session</button>
|
||||
</div>
|
||||
<div class="cell is-col-span-7">
|
||||
<input class="input" style="margin-bottom: 0" v-model="seed" placeholder="Seed" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid" v-if="!sessionId">
|
||||
<div class="cell">
|
||||
<button class="button" style="width: 200px" @click="joinSession">Join Session</button>
|
||||
</div>
|
||||
<div class="cell is-col-span-7">
|
||||
<input
|
||||
class="input"
|
||||
style="margin-bottom: 0"
|
||||
v-model="sessionId"
|
||||
placeholder="Session Id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 action-select"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="margin-top: 16px; display: none">
|
||||
<div>
|
||||
<!-- <ul id="messages"></ul> -->
|
||||
<form id="form" action="">
|
||||
<div class="action-select select">
|
||||
<select
|
||||
v-model="selectdAction"
|
||||
id="event"
|
||||
autocomplete="off"
|
||||
@change="actionSelected"
|
||||
>
|
||||
<option value="">Select event</option>
|
||||
<option :key="option.value" v-for="option in options" :value="option">
|
||||
{{ option.value }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click.prevent.stop="sendMessage">Send</button>
|
||||
</div>
|
||||
<!-- <p><input id="room" autocomplete="off" /></p> -->
|
||||
<p>
|
||||
<textarea
|
||||
v-model="data"
|
||||
id="message"
|
||||
autocomplete="off"
|
||||
placeholder="Data"
|
||||
></textarea>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<textarea
|
||||
:value="responseField"
|
||||
id="response"
|
||||
autocomplete="off"
|
||||
placeholder="Response"
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<textarea
|
||||
:value="statusField"
|
||||
id="status"
|
||||
autocomplete="off"
|
||||
placeholder="Game status"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End hand-->
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -258,21 +216,32 @@ function handleResponse(response: any) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action-select {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.control {
|
||||
font-size: 60%;
|
||||
padding: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
height: 110px;
|
||||
resize: none;
|
||||
font-size: 70%;
|
||||
}
|
||||
#response,
|
||||
#status {
|
||||
height: 400px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.tiles-container {
|
||||
|
@ -9,16 +9,21 @@ function startGame() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="home">
|
||||
<h1>Welcome to the Player's Home Page</h1>
|
||||
<p>This is a protected route.</p>
|
||||
<button @click="startGame">Start Game</button>
|
||||
<div class="block home">
|
||||
<section class="section">
|
||||
<h1 class="title is-2">Welcome to the Player's Home Page</h1>
|
||||
<div class="block">
|
||||
<p>This is a protected route.</p>
|
||||
</div>
|
||||
<button class="button" @click="startGame">Start Game</button>
|
||||
</section>
|
||||
<section class="section available-sessions">
|
||||
<h2 class="title is-4">Available Sessions</h2>
|
||||
<div class="bloc">
|
||||
<p>There are no available sessions at the moment.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.home {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
@ -19,10 +19,28 @@ function login() {
|
||||
<template>
|
||||
<div class="login">
|
||||
<h1>Login</h1>
|
||||
<form @submit.prevent="login">
|
||||
<input type="text" v-model="username" placeholder="Username" />
|
||||
<input type="password" v-model="password" placeholder="Password" />
|
||||
<button type="submit">Login</button>
|
||||
<form class="form" @submit.prevent="login">
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="username" placeholder="Username" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" v-model="password" placeholder="Password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="submit">Login</button>
|
||||
</div>
|
||||
<!-- <div class="control">
|
||||
<button class="button">Cancel</button>
|
||||
</div> -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
Reference in New Issue
Block a user