puzzle/src/server/Puzzle.ts

244 lines
6.6 KiB
TypeScript
Raw Normal View History

import Util from './../common/Util'
import { Rng } from './../common/Rng'
import Images from './Images'
2021-06-03 09:07:57 +02:00
import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle, ShapeMode } from '../common/Types'
2021-05-29 15:36:03 +02:00
import { Dim, Point } from '../common/Geometry'
export interface PuzzleCreationImageInfo {
file: string
url: string
}
2020-11-08 14:13:43 +01:00
2021-05-29 17:58:05 +02:00
export interface PuzzleCreationInfo {
2021-05-17 00:27:47 +02:00
width: number
height: number
tileSize: number
tileMarginWidth: number
tileDrawSize: number
tiles: number
tilesX: number
tilesY: number
}
2020-11-08 14:13:43 +01:00
// cut size of each puzzle tile in the
// final resized version of the puzzle image
const TILE_SIZE = 64
2020-12-21 18:34:57 +01:00
async function createPuzzle(
2021-05-17 00:27:47 +02:00
rng: Rng,
targetTiles: number,
2021-05-29 15:36:03 +02:00
image: PuzzleCreationImageInfo,
2021-06-03 09:07:57 +02:00
ts: number,
shapeMode: ShapeMode
2021-05-28 23:01:00 +02:00
): Promise<Puzzle> {
2020-11-25 22:03:35 +01:00
const imagePath = image.file
const imageUrl = image.url
2020-11-08 14:49:34 +01:00
// determine puzzle information from the image dimensions
const dim = await Images.getDimensions(imagePath)
2021-05-28 23:01:00 +02:00
if (!dim.w || !dim.h) {
2021-05-17 00:27:47 +02:00
throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`
}
2021-05-29 15:36:03 +02:00
const info: PuzzleCreationInfo = determinePuzzleInfo(dim, targetTiles)
2020-11-08 14:13:43 +01:00
2021-05-29 17:58:05 +02:00
const rawPieces = new Array(info.tiles)
for (let i = 0; i < rawPieces.length; i++) {
rawPieces[i] = { idx: i }
2020-11-08 14:13:43 +01:00
}
2021-06-03 09:07:57 +02:00
const shapes = determinePuzzleTileShapes(rng, info, shapeMode)
2020-11-08 14:13:43 +01:00
2021-05-28 23:01:00 +02:00
let positions: Point[] = new Array(info.tiles)
2021-05-29 17:58:05 +02:00
for (const piece of rawPieces) {
const coord = Util.coordByPieceIdx(info, piece.idx)
positions[piece.idx] = {
2020-11-08 16:42:59 +01:00
// instead of info.tileSize, we use info.tileDrawSize
// to spread the tiles a bit
2020-12-05 19:45:34 +01:00
x: coord.x * info.tileSize * 1.5,
y: coord.y * info.tileSize * 1.5,
2020-11-08 16:42:59 +01:00
}
}
2020-11-16 21:43:49 +01:00
const tableWidth = info.width * 3
const tableHeight = info.height * 3
2020-11-08 16:42:59 +01:00
2020-11-16 21:43:49 +01:00
const off = info.tileSize * 1.5
2021-05-29 17:58:05 +02:00
const last: Point = {
2020-11-16 21:43:49 +01:00
x: info.width - (1 * off),
y: info.height - (2 * off),
}
let countX = Math.ceil(info.width / off) + 2
let countY = Math.ceil(info.height / off) + 2
2020-11-08 16:42:59 +01:00
2020-11-16 21:43:49 +01:00
let diffX = off
let diffY = 0
2020-11-08 16:42:59 +01:00
let index = 0
2021-05-29 17:58:05 +02:00
for (const pos of positions) {
2020-11-08 16:42:59 +01:00
pos.x = last.x
pos.y = last.y
2020-11-16 21:43:49 +01:00
last.x+=diffX
last.y+=diffY
2020-11-08 16:42:59 +01:00
index++
// did we move horizontally?
2020-11-16 21:43:49 +01:00
if (diffX !== 0) {
if (index === countX) {
diffY = diffX
countY++
diffX = 0
2020-11-08 16:42:59 +01:00
index = 0
}
} else {
2020-11-16 21:43:49 +01:00
if (index === countY) {
diffX = -diffY
countX++
diffY = 0
2020-11-08 16:42:59 +01:00
index = 0
}
}
}
// then shuffle the positions
positions = rng.shuffle(positions)
2020-11-08 16:42:59 +01:00
2021-05-29 17:58:05 +02:00
const pieces: Array<EncodedPiece> = rawPieces.map(piece => {
2021-05-29 15:36:03 +02:00
return Util.encodePiece({
2021-05-29 17:58:05 +02:00
idx: piece.idx, // index of tile in the array
2020-11-08 16:42:59 +01:00
group: 0, // if grouped with other tiles
z: 0, // z index of the tile
// who owns the tile
// 0 = free for taking
// -1 = finished
// other values: id of player who has the tile
owner: 0,
// physical current position of the tile (x/y in pixels)
// this position is the initial position only and is the
// value that changes when moving a tile
2021-05-29 17:58:05 +02:00
pos: positions[piece.idx],
2020-12-05 19:45:34 +01:00
})
2020-11-08 16:42:59 +01:00
})
2020-11-08 14:13:43 +01:00
// Complete puzzle object
2020-11-16 21:43:49 +01:00
return {
2020-11-08 14:13:43 +01:00
// tiles array
2021-05-17 02:32:33 +02:00
tiles: pieces,
2020-11-08 14:13:43 +01:00
// game data for puzzle, data changes during the game
data: {
// TODO: maybe calculate this each time?
maxZ: 0, // max z of all pieces
maxGroup: 0, // max group of all pieces
2020-12-22 22:35:09 +01:00
started: ts, // start timestamp
finished: 0, // finish timestamp
2020-11-08 14:13:43 +01:00
},
// static puzzle information. stays same for complete duration of
// the game
info: {
2020-11-08 16:42:59 +01:00
table: {
width: tableWidth,
height: tableHeight,
},
2020-11-08 14:13:43 +01:00
// information that was used to create the puzzle
targetTiles: targetTiles,
2020-11-16 21:43:49 +01:00
imageUrl,
2020-11-08 14:13:43 +01:00
width: info.width, // actual puzzle width (same as bitmap.width)
height: info.height, // actual puzzle height (same as bitmap.height)
tileSize: info.tileSize, // width/height of each tile (without tabs)
tileDrawSize: info.tileDrawSize, // width/height of each tile (with tabs)
tileMarginWidth: info.tileMarginWidth,
// offset in x and y when drawing tiles, so that they appear to be at pos
tileDrawOffset: (info.tileDrawSize - info.tileSize) / -2,
// max distance between tile and destination that
// makes the tile snap to destination
snapDistance: info.tileSize / 2,
tiles: info.tiles, // the final number of tiles in the puzzle
2020-11-16 21:43:49 +01:00
tilesX: info.tilesX, // number of tiles each row
tilesY: info.tilesY, // number of tiles each col
2020-11-08 14:13:43 +01:00
// ( index => {x, y} )
// this is not the physical coordinate, but
// the tile_coordinate
// this can be used to determine where the
// final destination of a tile is
shapes: shapes, // tile shapes
},
}
}
2021-06-03 09:07:57 +02:00
function determineTabs (shapeMode: ShapeMode): number[] {
switch(shapeMode) {
case ShapeMode.ANY:
return [-1, 0, 1]
case ShapeMode.FLAT:
return [0]
case ShapeMode.NORMAL:
default:
return [-1, 1]
}
}
2020-11-08 14:13:43 +01:00
2020-12-21 18:34:57 +01:00
function determinePuzzleTileShapes(
2021-05-17 00:27:47 +02:00
rng: Rng,
2021-06-03 09:07:57 +02:00
info: PuzzleCreationInfo,
shapeMode: ShapeMode
2021-05-17 02:32:33 +02:00
): Array<EncodedPieceShape> {
2021-06-03 09:07:57 +02:00
const tabs: number[] = determineTabs(shapeMode)
2021-05-17 02:32:33 +02:00
const shapes: Array<PieceShape> = new Array(info.tiles)
2020-11-08 14:13:43 +01:00
for (let i = 0; i < info.tiles; i++) {
2021-05-29 17:58:05 +02:00
const coord = Util.coordByPieceIdx(info, i)
2020-12-06 00:14:55 +01:00
shapes[i] = {
2020-12-05 19:45:34 +01:00
top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1,
right: coord.x === info.tilesX - 1 ? 0 : rng.choice(tabs),
2020-12-05 19:45:34 +01:00
left: coord.x === 0 ? 0 : shapes[i - 1].right * -1,
bottom: coord.y === info.tilesY - 1 ? 0 : rng.choice(tabs),
2020-12-06 00:14:55 +01:00
}
2020-11-08 14:13:43 +01:00
}
2020-12-06 00:14:55 +01:00
return shapes.map(Util.encodeShape)
2020-11-08 14:13:43 +01:00
}
2021-05-29 15:36:03 +02:00
const determineTilesXY = (
dim: Dim,
targetTiles: number
): { tilesX: number, tilesY: number } => {
const w_ = dim.w < dim.h ? (dim.w * dim.h) : (dim.w * dim.w)
const h_ = dim.w < dim.h ? (dim.h * dim.h) : (dim.w * dim.h)
2020-11-16 21:43:49 +01:00
let size = 0
2020-11-08 14:13:43 +01:00
let tiles = 0
do {
2020-11-16 21:43:49 +01:00
size++
tiles = Math.floor(w_ / size) * Math.floor(h_ / size)
2020-11-08 14:13:43 +01:00
} while (tiles >= targetTiles)
2020-11-16 21:43:49 +01:00
size--
return {
tilesX: Math.round(w_ / size),
tilesY: Math.round(h_ / size),
}
}
2020-11-08 14:13:43 +01:00
2021-05-17 02:32:33 +02:00
const determinePuzzleInfo = (
2021-05-29 15:36:03 +02:00
dim: Dim,
2021-05-17 02:32:33 +02:00
targetTiles: number
2021-05-28 23:01:00 +02:00
): PuzzleCreationInfo => {
2021-05-29 15:36:03 +02:00
const {tilesX, tilesY} = determineTilesXY(dim, targetTiles)
2020-11-16 21:43:49 +01:00
const tiles = tilesX * tilesY
const tileSize = TILE_SIZE
const width = tilesX * tileSize
const height = tilesY * tileSize
2020-11-08 14:13:43 +01:00
const tileMarginWidth = tileSize * .5;
const tileDrawSize = Math.round(tileSize + tileMarginWidth * 2)
return {
width,
height,
tileSize,
tileMarginWidth,
tileDrawSize,
tiles,
2020-11-16 21:43:49 +01:00
tilesX,
tilesY,
2020-11-08 14:13:43 +01:00
}
}
export {
2020-11-12 19:19:02 +01:00
createPuzzle,
2020-11-08 14:13:43 +01:00
}