Skip to content

Commit 58ad91c

Browse files
committed
chore: add domino codebase
1 parent 44fa718 commit 58ad91c

25 files changed

+1053
-40
lines changed

β€Žpackages/cli/src/commands/dev/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export async function runDevServer(entryFile: string = 'src/main.ts') {
7272
deps: string[],
7373
callback: HotCallback['fn'] = () => {}
7474
) {
75+
// console.debug('accept dep', deps)
7576
const mod = hotModulesMap.get(fullPath) || {
7677
id: ownerPath,
7778
callbacks: [],
@@ -122,14 +123,15 @@ export async function runDevServer(entryFile: string = 'src/main.ts') {
122123
const entryPoint = resolve('.' + entryPointId)
123124

124125
server.watcher.on('change', async (fullPath) => {
125-
// console.error('changed file', fullPath)
126126
const existingModule = hotModulesMap.get(fullPath)
127127

128128
// full reload, tell the app to stop
129129
if (fullPath === entryPoint) {
130130
// throw new InvalidateSignal()
131131
}
132132

133+
// console.error('changed file', fullPath, existingModule)
134+
133135
if (existingModule) {
134136
const newModule = await runner.directRequest(
135137
existingModule.id,

β€Žpackages/cli/vite.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ export default defineConfig({
77
define: {
88
__DEV__: `process.env.NODE_ENV === "development"`,
99
},
10+
1011
resolve: {
1112
alias: {
1213
// Use development version instead of dist
1314
'vue-termui': resolve('../core/src/index.ts'),
1415
},
1516
},
17+
1618
plugins: [VueTermui()],
1719
})

β€Žpackages/domino/components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import '@vue/runtime-core'
66
declare module '@vue/runtime-core' {
77
export interface GlobalComponents {
88
Box: typeof import('vue-termui')['TuiBox']
9+
DominoTile: typeof import('./src/components/DominoTile.vue')['default']
10+
HandDominoTile: typeof import('./src/components/HandDominoTile.vue')['default']
911
Text: typeof import('vue-termui')['TuiText']
1012
}
1113
}

β€Žpackages/domino/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
},
99
"scripts": {
1010
"dev": "esmo ../cli/src/cli.ts dev src/main.ts",
11-
"build": "esmo ../cli/src/cli.ts build"
11+
"build": "esmo ../cli/src/cli.ts build",
12+
"test": "vitest run",
13+
"test:dev": "vitest"
1214
},
1315
"dependencies": {
1416
"vue-termui": "workspace:*"

β€Žpackages/domino/src/App.vue

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,14 @@
11
<script lang="ts" setup>
2-
// All imports are automatic but if you want to import anything,
3-
// remember to import from 'vue-termui':
4-
// import { ref } from 'vue-termui'
5-
6-
const n = ref(0)
7-
8-
const counter = ref(0)
9-
onKeyData(['+', 'ArrowRight', 'ArrowUp'], () => {
10-
counter.value++
11-
})
12-
onKeyData(['-', 'ArrowLeft', 'ArrowDown'], () => {
13-
counter.value--
14-
})
15-
16-
useInterval(() => {
17-
n.value++
18-
}, 600)
2+
import Game from './views/Game.vue'
193
</script>
204

215
<template>
226
<Box
23-
:padding="2"
24-
:margin="2"
257
width="100%"
26-
:maxWidth="50"
278
justifyContent="center"
289
alignItems="center"
2910
flexDirection="column"
30-
borderColor="yellowBright"
31-
borderStyle="round"
3211
>
33-
<Box :marginY="1">
34-
<Text color="cyanBright">Hello World </Text>
35-
<Text>{{ n % 2 ? 'πŸ‘‹ ' : ' βœ‹' }}</Text>
36-
</Box>
37-
<Box>
38-
<Text
39-
>Counter:
40-
<Text :color="counter < 0 ? 'red' : 'green'" bold>{{ counter }}</Text>
41-
<Text dimmed>
42-
(<Text color="red" bold>-</Text>/<Text bold color="green">+</Text> to
43-
change it)</Text
44-
>
45-
</Text>
46-
</Box>
12+
<Game />
4713
</Box>
4814
</template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts" setup>
2+
import { DominoTile, DominoTileDirection } from '../engine'
3+
4+
const props = defineProps<{ tile: DominoTile; hidden?: boolean }>()
5+
const hiddenTile = computed(() =>
6+
props.tile.direction === DominoTileDirection.horizontal ? 'πŸ€°' : '🁒'
7+
)
8+
</script>
9+
10+
<template>
11+
<Text>{{ hidden ? hiddenTile : tile }}</Text>
12+
</template>
13+
14+
<!-- <Box borderStyle="round" flexDirection="column">
15+
<Text> β Ώ </Text>
16+
<Text>-β‹…-</Text>
17+
<Text> β Ώ </Text>
18+
</Box> -->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script lang="ts" setup>
2+
import { DominoTile, DominoTileDirection } from '../engine'
3+
import { useFocus, Ref } from 'vue-termui'
4+
5+
const props = defineProps<{
6+
tile: DominoTile
7+
hidden?: boolean
8+
disabled?: boolean
9+
}>()
10+
11+
const { active } = useFocus({
12+
disabled: toRef(props, 'disabled') as Ref<boolean>,
13+
})
14+
</script>
15+
16+
<template>
17+
<Text
18+
><Text :inverse="active">{{ tile }}</Text
19+
>&nbsp;</Text
20+
>
21+
</template>
22+
23+
<!-- <Box borderStyle="round" flexDirection="column">
24+
<Text> β Ώ </Text>
25+
<Text>-β‹…-</Text>
26+
<Text> β Ώ </Text>
27+
</Box> -->
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
describe('DominoGame', () => {
2+
it('must work', () => {
3+
expect(42).toBe(42)
4+
})
5+
})
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { DominoHalfTileValue, DominoTile } from './DominoTile'
2+
import { DominoTileBoard } from './DominoTileBoard'
3+
import { DominoTilePile } from './DominoTilePile'
4+
import { EventEmitter } from './Emitter'
5+
import { Player } from './Player'
6+
7+
export type DominoGameEvents = {
8+
playerSkip: Player
9+
playerPlay: [Player, DominoTile]
10+
playerUpdate: Player
11+
playerLocked: [Player, DominoHalfTileValue]
12+
13+
boardUpdate: DominoTileBoard
14+
15+
gameStart?: never
16+
gameEnd: [Player, number] | []
17+
}
18+
19+
export class DominoGame extends EventEmitter<DominoGameEvents> {
20+
nextPlayerIndex = 0
21+
players: Player[] = []
22+
tilesPile = new DominoTilePile()
23+
24+
board = new DominoTileBoard()
25+
26+
constructor(...players: string[]) {
27+
super()
28+
this.players = players.map((p) => new Player(p))
29+
this.nextPlayerIndex = 0
30+
if (this.players.length > 4 || this.players.length < 2) {
31+
throw new Error('Can only have between 2 and 4 players.')
32+
}
33+
34+
this.tilesPile.shuffle()
35+
this.players.forEach((player, i) => {
36+
player.addToHand(this.tilesPile.pull(7))
37+
this.emit('playerUpdate', player)
38+
if (player.hasTile(6, 6)) {
39+
this.nextPlayerIndex = i
40+
}
41+
})
42+
43+
this.emit('boardUpdate', this.board)
44+
}
45+
46+
play(tile: DominoTile) {
47+
if (this.currentPlayer.hasTile(tile) && this.board.canPlaceTile(tile)) {
48+
const targetTile = this.currentPlayer.useTile(tile)
49+
this.board.placeTile(targetTile)
50+
this.emit('playerPlay', [this.currentPlayer, tile])
51+
this.emit('boardUpdate', this.board)
52+
53+
if (this.isOver()) {
54+
if (this.isLocked()) {
55+
this.emit('playerLocked', [
56+
this.currentPlayer,
57+
this.board.tiles[0].start,
58+
])
59+
}
60+
const winner = this.getWinner()
61+
this.emit('gameEnd', winner ? [winner, this.getWinnerPoints()] : [])
62+
} else {
63+
this.nextPlayerIndex = (this.nextPlayerIndex + 1) % this.players.length
64+
// TODO: draw from pile
65+
while (!this.getPossibleTiles().length) {
66+
this.emit('playerSkip', this.currentPlayer)
67+
this.nextPlayerIndex =
68+
(this.nextPlayerIndex + 1) % this.players.length
69+
}
70+
}
71+
}
72+
}
73+
74+
/**
75+
* Randomly picks a playable tile for the player and plays it.
76+
*/
77+
playRandomly() {
78+
const possibleTiles = this.getPossibleTiles()
79+
if (!possibleTiles.length) return
80+
const i = Math.floor(Math.random() * possibleTiles.length)
81+
this.play(possibleTiles[i])
82+
}
83+
84+
isLocked() {
85+
return this.players.every((p) =>
86+
p.hand.every((t) => !this.board.canPlaceTile(t))
87+
)
88+
}
89+
90+
isOver() {
91+
return this.isLocked() || this.players.find((player) => !player.hand.length)
92+
}
93+
94+
getWinner(): Player | undefined {
95+
const isOver = this.isOver()
96+
if (isOver === true) {
97+
// TODO: count per pairs or single
98+
const pair1Points =
99+
this.players[0].getHandPoints() + this.players[2].getHandPoints()
100+
const pair2Points =
101+
this.players[1].getHandPoints() + this.players[3].getHandPoints()
102+
103+
if (pair1Points === pair2Points) {
104+
return undefined
105+
}
106+
107+
return pair1Points < pair2Points ? this.players[0] : this.players[1]
108+
}
109+
110+
// cannot return a boolean because we checked for isLocked()
111+
return isOver
112+
}
113+
114+
getWinnerPoints(): number {
115+
return Math.floor(
116+
this.players.reduce(
117+
(total, player) => total + player.getHandPoints(),
118+
0
119+
) / 10
120+
)
121+
}
122+
123+
get currentPlayer() {
124+
return this.players[this.nextPlayerIndex]
125+
}
126+
127+
getPossibleTiles() {
128+
return this.currentPlayer.hand.filter((tile) =>
129+
this.board.canPlaceTile(tile)
130+
)
131+
}
132+
133+
getState() {
134+
this.board.tiles // per id 0:1 === 1:0 === 1
135+
this.getPossibleTiles() // per id
136+
// other players hand size
137+
}
138+
139+
toString() {
140+
return `---
141+
Pile: ${this.tilesPile}
142+
Board: ${this.board}
143+
Next player: ${this.players[this.nextPlayerIndex]}
144+
Possible plays: ${this.getPossibleTiles().join('') || '<None>'}${
145+
this.isLocked() ? ' ❌ <Locked>' : ''
146+
}
147+
---
148+
${this.players.join('\n')}`
149+
}
150+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { DominoTile, DominoTileDirection } from './DominoTile'
2+
3+
describe('DominoTile', () => {
4+
it('can be turned', () => {
5+
const tile = new DominoTile(0, 2)
6+
expect(tile.value).toBe('0:2')
7+
expect(tile.turn().value).toBe('2:0')
8+
expect(tile.value).toBe('2:0')
9+
})
10+
11+
it('can be cloned', () => {
12+
const tile = new DominoTile(0, 2)
13+
const cloned = tile.clone(DominoTileDirection.vertical)
14+
tile.turn()
15+
expect(cloned.value).not.toBe(tile.value)
16+
})
17+
18+
it('can get parts', () => {
19+
const tile = new DominoTile(0, 2)
20+
expect(tile.start).toBe(0)
21+
expect(tile.end).toBe(2)
22+
})
23+
24+
it('can be stringified', () => {
25+
const tile = new DominoTile(0, 2).clone(DominoTileDirection.horizontal)
26+
expect(`${tile}`).toBe('πŸ€³')
27+
tile.turn()
28+
expect(`${tile}`).toBe('πŸ€Ώ')
29+
const clone = tile.clone(DominoTileDirection.vertical)
30+
expect(`${clone}`).toBe('🁱')
31+
clone.turn()
32+
expect(`${clone}`).toBe('πŸ₯')
33+
})
34+
35+
it('can be created from string', () => {
36+
function testString(domino: string) {
37+
expect(DominoTile.fromString(domino).toString()).toBe(domino)
38+
}
39+
testString('πŸ€³')
40+
testString('πŸ€±')
41+
testString('πŸ€Ή')
42+
testString('πŸ€Ό')
43+
testString('πŸ‹')
44+
testString('πŸ›')
45+
46+
testString('🁣')
47+
testString('🁲')
48+
testString('πŸ‚“')
49+
testString('πŸ‚')
50+
testString('🁿')
51+
testString('🁻')
52+
})
53+
})

0 commit comments

Comments
Β (0)