-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
299 additions
and
5 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<template> | ||
<h3 class="mt-4 mb-3">{{t('setup.randomMap.title')}}</h3> | ||
|
||
<p v-if="!show"> | ||
<button class="btn btn-sm btn-outline-secondary" @click="showRandomizer">{{t('setup.randomMap.show')}}</button> | ||
</p> | ||
<div v-if="show"> | ||
<div class="map"> | ||
<div v-for="item of tilePlacements" :key="item.tile.name" | ||
:style="{position:'absolute',left:`${item.x*100}px`,top:`${item.y*100}px`}"> | ||
<div class="letter">{{item.tile.name}}</div> | ||
<AppIcon type="map-tile" :name="item.tile.name" | ||
:style="{width:'100px','transform-origin':'50px 50px',transform:getTransform(item)}"/> | ||
</div> | ||
</div> | ||
|
||
<div class="d-flex mt-2"> | ||
<button class="btn btn-sm btn-outline-secondary" @click="randomizeMapTiles">{{t('setup.randomMap.randomize')}}</button> | ||
<div class="form-check ms-2"> | ||
<label class="form-check-label small"> | ||
<input class="form-check-input" type="checkbox" :value="true" v-model="state.setup.mapFirstPlay" @change="randomizeMapTiles"> | ||
{{t('setup.randomMap.firstPlay')}} | ||
</label> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, ref } from 'vue' | ||
import { useI18n } from 'vue-i18n' | ||
import AppIcon from '../structure/AppIcon.vue' | ||
import { useStateStore } from '@/store/state'; | ||
import MapRandomizer, { Rotation, TilePlacement } from '@/services/MapRandomizer'; | ||
export default defineComponent({ | ||
name: 'RandomMap', | ||
components: { | ||
AppIcon | ||
}, | ||
setup() { | ||
const { t } = useI18n() | ||
const state = useStateStore() | ||
const tilePlacements = ref([] as readonly TilePlacement[]) | ||
const randomizeMapTiles = function() { | ||
const mapRandomizer = new MapRandomizer(state.setup.mapFirstPlay ?? true) | ||
mapRandomizer.randomize() | ||
tilePlacements.value = mapRandomizer.tiles | ||
} | ||
return { t, state, tilePlacements, randomizeMapTiles } | ||
}, | ||
data() { | ||
return { | ||
show: false | ||
} | ||
}, | ||
methods: { | ||
showRandomizer() : void { | ||
this.randomizeMapTiles() | ||
this.show = true | ||
}, | ||
getTransform(tilePlacement: TilePlacement) : string { | ||
let x = 0 | ||
let y = 0 | ||
if (tilePlacement.rotation == Rotation.RIGHT) { | ||
x = 100 | ||
} | ||
else if (tilePlacement.rotation == Rotation.DOWN) { | ||
y = 100 | ||
} | ||
return `translate(${x}px,${y}px) rotate(${tilePlacement.rotation}deg)` | ||
} | ||
} | ||
}) | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
.map { | ||
position: relative; | ||
border: 1px solid #aaa; | ||
width: 501px; | ||
max-width: 100%; | ||
height: 301px; | ||
overflow-x: auto; | ||
overflow-y: hidden; | ||
border-radius: 10px; | ||
background-color: #eee; | ||
.letter { | ||
position: absolute; | ||
top: 50px; | ||
left: 50%; | ||
transform: translate(-50%, -50%); | ||
text-transform: uppercase; | ||
z-index: 100; | ||
font-size: 20px; | ||
font-weight: bold; | ||
text-shadow: -2px -2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, 2px 2px 0 #fff; | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import rollDice from '@brdgm/brdgm-commons/src/util/random/rollDice' | ||
|
||
/** | ||
* Manages the bot actions. | ||
*/ | ||
export default class MapRandomizer { | ||
|
||
readonly _firstPlay : boolean | ||
private _tiles : TilePlacement[] = [] | ||
|
||
public constructor(firstPlay : boolean) { | ||
this._firstPlay = firstPlay | ||
} | ||
|
||
public randomize() : void { | ||
let retryCount = 0 | ||
while (retryCount < MAX_RETRIES) { | ||
this._tiles = [] | ||
if (this._firstPlay) { | ||
this._tiles.push({ tile: MAP_TILES[0], x: 1, y: 1, rotation: Rotation.UP }) | ||
this._tiles.push({ tile: MAP_TILES[1], x: 2, y: 0, rotation: Rotation.UP }) | ||
this._tiles.push({ tile: MAP_TILES[2], x: 1, y: 2, rotation: Rotation.RIGHT }) | ||
} | ||
let failed = false | ||
for (let i=this._firstPlay ? 3 : 0; i<MAP_TILES.length; i++) { | ||
const positions = this.findValidPositions(MAP_TILES[i]) | ||
if (positions.length > 0) { | ||
const index = rollDice(positions.length) - 1 | ||
this._tiles.push(positions[index]) | ||
} | ||
else { | ||
failed = true | ||
break | ||
} | ||
} | ||
if (failed) { | ||
retryCount++ | ||
} | ||
else { | ||
return | ||
} | ||
} | ||
throw new Error('Unable to find valid map composition.') | ||
} | ||
|
||
private findValidPositions(tile: MapTile) : TilePlacement[] { | ||
if (tile.length == 1) { | ||
return this.findValidPositions_1Length(tile) | ||
} | ||
else { | ||
return this.findValidPositions_2Length(tile) | ||
} | ||
} | ||
|
||
private findValidPositions_1Length(tile: MapTile) : TilePlacement[] { | ||
const positions : TilePlacement[] = [] | ||
const matrix = getMatrix(this._tiles) | ||
for (let x=0; x<5; x++) { | ||
for (let y=0;y<3;y++) { | ||
if (!matrix[x][y]) { | ||
positions.push({ tile, x, y, rotation: Rotation.UP }) | ||
} | ||
} | ||
} | ||
return positions | ||
} | ||
|
||
private findValidPositions_2Length(tile: MapTile) : TilePlacement[] { | ||
const positions : TilePlacement[] = [] | ||
const matrix = getMatrix(this._tiles) | ||
// horizontal | ||
for (let x=0; x<4; x++) { | ||
for (let y=0;y<3;y++) { | ||
if (!matrix[x][y] && !matrix[x+1][y]) { | ||
positions.push({ tile, x, y, rotation: Rotation.RIGHT }) | ||
positions.push({ tile, x, y, rotation: Rotation.LEFT }) | ||
} | ||
} | ||
} | ||
// vertical | ||
for (let x=0; x<5; x++) { | ||
for (let y=0;y<2;y++) { | ||
if (!matrix[x][y] && !matrix[x][y+1]) { | ||
positions.push({ tile, x, y, rotation: Rotation.UP }) | ||
positions.push({ tile, x, y, rotation: Rotation.DOWN }) | ||
} | ||
} | ||
} | ||
return positions | ||
} | ||
|
||
public get tiles() : readonly TilePlacement[] { | ||
return this._tiles | ||
} | ||
|
||
} | ||
|
||
const MAX_RETRIES = 1000 | ||
|
||
const MAP_TILES : MapTile[] = [ | ||
{ name: 'a', length: 1 }, | ||
{ name: 'b', length: 2 }, | ||
{ name: 'c', length: 2 }, | ||
{ name: 'd', length: 2 }, | ||
{ name: 'e', length: 2 }, | ||
{ name: 'f', length: 2 }, | ||
{ name: 'g', length: 2 }, | ||
{ name: 'h', length: 2 }, | ||
] | ||
|
||
export interface MapTile { | ||
name: string | ||
length: number | ||
} | ||
|
||
export interface TilePlacement { | ||
tile: MapTile | ||
x: number | ||
y: number | ||
rotation: Rotation | ||
} | ||
|
||
export enum Rotation { | ||
UP = 0, | ||
RIGHT = 90, | ||
DOWN = 180, | ||
LEFT = 270 | ||
} | ||
|
||
function getMatrix(tilePlacements : TilePlacement[]) : boolean[][] { | ||
const matrix : boolean[][] = [] | ||
for (let x=0; x<5; x++) { | ||
matrix[x] = [false,false,false] | ||
} | ||
for (const tilePlacement of tilePlacements) { | ||
const { x1, x2, y1, y2 } = getCoordinates(tilePlacement) | ||
matrix[x1][y1] = true | ||
matrix[x1][y2] = true | ||
matrix[x2][y1] = true | ||
matrix[x2][y2] = true | ||
} | ||
return matrix | ||
} | ||
|
||
function getCoordinates(tilePlacement : TilePlacement) : { x1: number, x2: number, y1: number, y2: number } { | ||
const x1 = tilePlacement.x | ||
const y1 = tilePlacement.y | ||
let x2 = x1 | ||
let y2 = y1 | ||
if (tilePlacement.tile.length == 2) { | ||
if (tilePlacement.rotation == Rotation.LEFT || tilePlacement.rotation == Rotation.RIGHT) { | ||
x2 = x1 + 1 | ||
} | ||
else { | ||
y2 = y2 + 1 | ||
} | ||
} | ||
return { x1, x2, y1, y2 } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { expect } from 'chai' | ||
import MapRandomizer from '@/services/MapRandomizer' | ||
|
||
describe('services/MapRandomizer', () => { | ||
it('randomizeFirstPlay', () => { | ||
const mapRandomizer = new MapRandomizer(true) | ||
mapRandomizer.randomize() | ||
expect(mapRandomizer.tiles.length).to.eq(8) | ||
}) | ||
|
||
it('randomize', () => { | ||
const mapRandomizer = new MapRandomizer(false) | ||
mapRandomizer.randomize() | ||
expect(mapRandomizer.tiles.length).to.eq(8) | ||
}) | ||
}) |