puzzle/server/Puzzle.js

235 lines
6.3 KiB
JavaScript
Raw Normal View History

2020-11-08 14:13:43 +01:00
import sizeOf from 'image-size'
2020-11-12 19:25:42 +01:00
import Util from './../common/Util.js'
2020-12-09 00:15:04 +01:00
import exif from 'exif'
2020-12-21 18:34:57 +01:00
import { Rng } from '../common/Rng.js'
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-09 00:15:04 +01:00
async function getDimensions(imagePath) {
let dimensions = sizeOf(imagePath)
try {
const orientation = await getExifOrientation(imagePath)
// when image is rotated to the left or right, switch width/height
// https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
if (orientation === 6 || orientation === 8) {
return {
width: dimensions.height,
height: dimensions.width,
}
}
} catch {}
return dimensions
}
async function getExifOrientation(imagePath) {
return new Promise((resolve, reject) => {
new exif.ExifImage({ image: imagePath }, function (error, exifData) {
if (error) {
reject(error)
} else {
resolve(exifData.image.Orientation)
}
})
})
}
2020-12-21 18:34:57 +01:00
async function createPuzzle(
/** @type Rng */ rng,
targetTiles,
image
) {
2020-11-25 22:03:35 +01:00
const imagePath = image.file
const imageUrl = image.url
2020-11-08 14:49:34 +01:00
2020-11-08 14:13:43 +01:00
// load bitmap, to determine the original size of the image
2020-12-09 00:15:04 +01:00
const dim = await getDimensions(imagePath)
2020-11-08 14:13:43 +01:00
// determine puzzle information from the bitmap
2020-11-16 21:43:49 +01:00
const info = determinePuzzleInfo(dim.width, dim.height, targetTiles)
2020-11-08 14:13:43 +01:00
let tiles = new Array(info.tiles)
for (let i = 0; i < tiles.length; i++) {
2020-11-16 21:43:49 +01:00
tiles[i] = { idx: i }
2020-11-08 14:13:43 +01:00
}
2020-12-21 18:34:57 +01:00
const shapes = determinePuzzleTileShapes(rng, info)
2020-11-08 14:13:43 +01:00
2020-11-08 16:42:59 +01:00
let positions = new Array(info.tiles)
for (let tile of tiles) {
2020-12-05 19:45:34 +01:00
let coord = Util.coordByTileIdx(info, tile.idx)
2020-11-08 16:42:59 +01:00
positions[tile.idx] ={
// 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
let last = {
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
for (let pos of positions) {
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
2020-12-21 18:34:57 +01:00
positions = Util.shuffle(rng, positions)
2020-11-08 16:42:59 +01:00
tiles = tiles.map(tile => {
2020-12-05 19:45:34 +01:00
return Util.encodeTile({
2020-11-08 16:42:59 +01:00
idx: tile.idx, // index of tile in the array
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
pos: positions[tile.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
2020-11-08 16:42:59 +01:00
tiles,
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
started: Util.timestamp(), // 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
},
}
}
2020-12-21 18:34:57 +01:00
function determinePuzzleTileShapes(
/** @type Rng */ rng,
info
) {
2020-11-08 14:13:43 +01:00
const tabs = [-1, 1]
const shapes = new Array(info.tiles)
for (let i = 0; i < info.tiles; i++) {
2020-12-05 19:45:34 +01:00
let coord = Util.coordByTileIdx(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,
2020-12-21 18:34:57 +01:00
right: coord.x === info.tilesX - 1 ? 0 : Util.choice(rng, tabs),
2020-12-05 19:45:34 +01:00
left: coord.x === 0 ? 0 : shapes[i - 1].right * -1,
2020-12-21 18:34:57 +01:00
bottom: coord.y === info.tilesY - 1 ? 0 : Util.choice(rng, 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
}
2020-11-16 21:43:49 +01:00
const determineTilesXY = (w, h, targetTiles) => {
const w_ = w < h ? (w * h) : (w * w)
const h_ = w < h ? (h * h) : (w * h)
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
2020-11-16 21:43:49 +01:00
const determinePuzzleInfo = (w, h, targetTiles) => {
const {tilesX, tilesY} = determineTilesXY(w, h, targetTiles)
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
}