move puzzle generation to server
This commit is contained in:
parent
e74a6fd62f
commit
3917c591b0
7 changed files with 207 additions and 213 deletions
|
|
@ -17,23 +17,11 @@ export default class Camera {
|
||||||
}
|
}
|
||||||
|
|
||||||
rect() {
|
rect() {
|
||||||
// when no zoom is relevant:
|
|
||||||
return new BoundingRectangle(
|
return new BoundingRectangle(
|
||||||
this.x,
|
- this.x,
|
||||||
this.x + this.width - 1,
|
- this.x + (this.width / this.zoom),
|
||||||
this.y,
|
- this.y,
|
||||||
this.y + this.height - 1
|
- this.y + (this.height / this.zoom),
|
||||||
)
|
|
||||||
|
|
||||||
// when zoom is relevant:
|
|
||||||
// TODO: check if still true
|
|
||||||
const w_final = this.width * this.zoom
|
|
||||||
const h_final = this.height * this.zoom
|
|
||||||
return new BoundingRectangle(
|
|
||||||
this.x + (this.width - w_final) / 2,
|
|
||||||
this.x + (this.width + w_final) / 2,
|
|
||||||
this.y + (this.height - h_final) / 2,
|
|
||||||
this.y + (this.height + h_final) / 2
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
201
game/index.js
201
game/index.js
|
|
@ -5,23 +5,10 @@ import Bitmap from './Bitmap.js'
|
||||||
import {run} from './gameloop.js'
|
import {run} from './gameloop.js'
|
||||||
import Camera from './Camera.js'
|
import Camera from './Camera.js'
|
||||||
import EventAdapter from './EventAdapter.js'
|
import EventAdapter from './EventAdapter.js'
|
||||||
import { choice } from './util.js'
|
|
||||||
import WsClient from './WsClient.js'
|
import WsClient from './WsClient.js'
|
||||||
|
|
||||||
if (!WS_ADDRESS) throw '[ WS_ADDRESS not set ]'
|
if (!WS_ADDRESS) throw '[ WS_ADDRESS not set ]'
|
||||||
|
|
||||||
const TILE_SIZE = 64 // cut size of each puzzle tile in the
|
|
||||||
// final resized version of the puzzle image
|
|
||||||
const TARGET_TILES = 1000 // desired number of tiles
|
|
||||||
// actual calculated number can be higher
|
|
||||||
const IMAGES = [
|
|
||||||
'./example-images/ima_86ec3fa.jpeg',
|
|
||||||
'./example-images/bleu.png',
|
|
||||||
'./example-images/saechsische_schweiz.jpg',
|
|
||||||
'./example-images/132-2048x1365.jpg',
|
|
||||||
]
|
|
||||||
const IMAGE_URL = IMAGES[0]
|
|
||||||
|
|
||||||
function createCanvas(width = 0, height = 0) {
|
function createCanvas(width = 0, height = 0) {
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
canvas.width = width === 0 ? window.innerWidth : width
|
canvas.width = width === 0 ? window.innerWidth : width
|
||||||
|
|
@ -235,55 +222,6 @@ function pointInBounds(pt, rect) {
|
||||||
&& pt.y <= rect.y1
|
&& pt.y <= rect.y1
|
||||||
}
|
}
|
||||||
|
|
||||||
const determinePuzzleInfo = (w, h, targetTiles) => {
|
|
||||||
let tileSize = 0
|
|
||||||
let tiles = 0
|
|
||||||
do {
|
|
||||||
tileSize++
|
|
||||||
tiles = tilesFit(w, h, tileSize)
|
|
||||||
} while (tiles >= targetTiles)
|
|
||||||
tileSize--
|
|
||||||
|
|
||||||
tiles = tilesFit(w, h, tileSize)
|
|
||||||
const tiles_x = Math.round(w / tileSize)
|
|
||||||
const tiles_y = Math.round(h / tileSize)
|
|
||||||
tiles = tiles_x * tiles_y
|
|
||||||
|
|
||||||
// then resize to final TILE_SIZE (which is always the same)
|
|
||||||
tileSize = TILE_SIZE
|
|
||||||
const width = tiles_x * tileSize
|
|
||||||
const height = tiles_y * tileSize
|
|
||||||
const coords = coordsByNum({ width, height, tileSize, tiles })
|
|
||||||
|
|
||||||
const tileMarginWidth = tileSize * .5;
|
|
||||||
const tileDrawSize = Math.round(tileSize + tileMarginWidth * 2)
|
|
||||||
|
|
||||||
return {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
tileSize,
|
|
||||||
tileMarginWidth,
|
|
||||||
tileDrawSize,
|
|
||||||
tiles,
|
|
||||||
tiles_x,
|
|
||||||
tiles_y,
|
|
||||||
coords,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tilesFit = (w, h, size) => Math.floor(w / size) * Math.floor(h / size)
|
|
||||||
|
|
||||||
const coordsByNum = (puzzleInfo) => {
|
|
||||||
const w_tiles = puzzleInfo.width / puzzleInfo.tileSize
|
|
||||||
const coords = new Array(puzzleInfo.tiles)
|
|
||||||
for (let i = 0; i < puzzleInfo.tiles; i++) {
|
|
||||||
const y = Math.floor(i / w_tiles)
|
|
||||||
const x = i % w_tiles
|
|
||||||
coords[i] = { x, y }
|
|
||||||
}
|
|
||||||
return coords
|
|
||||||
}
|
|
||||||
|
|
||||||
const resizeBitmap = (bitmap, width, height) => {
|
const resizeBitmap = (bitmap, width, height) => {
|
||||||
const tmp = new Bitmap(width, height)
|
const tmp = new Bitmap(width, height)
|
||||||
mapBitmapToBitmap(
|
mapBitmapToBitmap(
|
||||||
|
|
@ -311,21 +249,6 @@ function getSurroundingTilesByIdx(puzzle, idx) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function determinePuzzleTileShapes(info) {
|
|
||||||
const tabs = [-1, 1]
|
|
||||||
|
|
||||||
const shapes = new Array(info.tiles)
|
|
||||||
for (let i = 0; i < info.tiles; i++) {
|
|
||||||
shapes[i] = {
|
|
||||||
top: info.coords[i].y === 0 ? 0 : shapes[i - info.tiles_x].bottom * -1,
|
|
||||||
right: info.coords[i].x === info.tiles_x - 1 ? 0 : choice(tabs),
|
|
||||||
left: info.coords[i].x === 0 ? 0 : shapes[i - 1].right * -1,
|
|
||||||
bottom: info.coords[i].y === info.tiles_y - 1 ? 0 : choice(tabs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shapes
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPuzzleTileBitmaps(bitmap, tiles, info) {
|
async function createPuzzleTileBitmaps(bitmap, tiles, info) {
|
||||||
let img = await bitmap.toImage()
|
let img = await bitmap.toImage()
|
||||||
var tileSize = info.tileSize
|
var tileSize = info.tileSize
|
||||||
|
|
@ -475,82 +398,6 @@ async function loadPuzzleBitmaps(puzzle) {
|
||||||
return await createPuzzleTileBitmaps(bmpResized, puzzle.tiles, puzzle.info)
|
return await createPuzzleTileBitmaps(bmpResized, puzzle.tiles, puzzle.info)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPuzzle(targetTiles, imageUrl) {
|
|
||||||
// load bitmap, to determine the original size of the image
|
|
||||||
let bmp = await loadImageToBitmap(imageUrl)
|
|
||||||
|
|
||||||
// determine puzzle information from the bitmap
|
|
||||||
let info = determinePuzzleInfo(bmp.width, bmp.height, targetTiles)
|
|
||||||
|
|
||||||
let tiles = new Array(info.tiles)
|
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
|
||||||
tiles[i] = {
|
|
||||||
idx: i,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const shapes = determinePuzzleTileShapes(info)
|
|
||||||
|
|
||||||
// Complete puzzle object
|
|
||||||
const p = {
|
|
||||||
// tiles array
|
|
||||||
tiles: tiles.map(tile => {
|
|
||||||
return {
|
|
||||||
idx: tile.idx, // index of tile in the array
|
|
||||||
group: 0, // if grouped with other tiles
|
|
||||||
z: 0, // z index of the tile
|
|
||||||
owner: 0, // who owns the tile
|
|
||||||
// 0 = free for taking
|
|
||||||
// -1 = finished
|
|
||||||
// other values: id of player who has the tile
|
|
||||||
// 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
|
|
||||||
// TODO: scatter the tiles on the table at the beginning
|
|
||||||
pos: {
|
|
||||||
x: info.coords[tile.idx].x * info.tileSize,
|
|
||||||
y: info.coords[tile.idx].y * info.tileSize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
// 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
|
|
||||||
},
|
|
||||||
// static puzzle information. stays same for complete duration of
|
|
||||||
// the game
|
|
||||||
info: {
|
|
||||||
// information that was used to create the puzzle
|
|
||||||
targetTiles: targetTiles,
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
|
|
||||||
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
|
|
||||||
tiles_x: info.tiles_x, // number of tiles each row
|
|
||||||
tiles_y: info.tiles_y, // number of tiles each col
|
|
||||||
coords: info.coords, // map of tile index to its coordinates
|
|
||||||
// ( 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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function uniqId() {
|
function uniqId() {
|
||||||
return Date.now().toString(36) + Math.random().toString(36).substring(2)
|
return Date.now().toString(36) + Math.random().toString(36).substring(2)
|
||||||
}
|
}
|
||||||
|
|
@ -584,27 +431,10 @@ async function main () {
|
||||||
conn.send(JSON.stringify({ type: 'init' }))
|
conn.send(JSON.stringify({ type: 'init' }))
|
||||||
conn.onSocket('message', async ({data}) => {
|
conn.onSocket('message', async ({data}) => {
|
||||||
const d = JSON.parse(data)
|
const d = JSON.parse(data)
|
||||||
let game
|
|
||||||
if (d.type === 'init') {
|
if (d.type === 'init') {
|
||||||
game = d.game
|
console.log('the game ', d.game)
|
||||||
if (game.puzzle) {
|
let bitmaps = await loadPuzzleBitmaps(d.game.puzzle)
|
||||||
console.log('loaded from server')
|
startGame(d.game, bitmaps, conn)
|
||||||
} else {
|
|
||||||
// The game doesnt exist yet on the server, so load puzzle
|
|
||||||
// and then give the server some info about the puzzle
|
|
||||||
// Load puzzle and determine information about it
|
|
||||||
// TODO: move puzzle creation to server
|
|
||||||
game.puzzle = await createPuzzle(TARGET_TILES, IMAGE_URL)
|
|
||||||
conn.send(JSON.stringify({
|
|
||||||
type: 'init_puzzle',
|
|
||||||
puzzle: game.puzzle,
|
|
||||||
}))
|
|
||||||
console.log('loaded from local config')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('the game ', game)
|
|
||||||
let bitmaps = await loadPuzzleBitmaps(game.puzzle)
|
|
||||||
startGame(game, bitmaps, conn)
|
|
||||||
} else {
|
} else {
|
||||||
// console.log(d)
|
// console.log(d)
|
||||||
}
|
}
|
||||||
|
|
@ -1153,22 +983,23 @@ async function main () {
|
||||||
|
|
||||||
// TODO: improve the rendering
|
// TODO: improve the rendering
|
||||||
// atm it is pretty slow (~40-50ms)
|
// atm it is pretty slow (~40-50ms)
|
||||||
mapBitmapToAdapter(puzzleTable, new BoundingRectangle(
|
mapBitmapToAdapter(
|
||||||
- cam.x,
|
puzzleTable,
|
||||||
- cam.x + (cam.width / cam.zoom),
|
cam.rect(),
|
||||||
- cam.y,
|
adapter,
|
||||||
- cam.y + (cam.height / cam.zoom),
|
adapter.getBoundingRect()
|
||||||
), adapter, adapter.getBoundingRect())
|
)
|
||||||
checkpoint('to_adapter_1')
|
checkpoint('to_adapter_1')
|
||||||
} else if (rerenderPlayer) {
|
} else if (rerenderPlayer) {
|
||||||
adapter.clearRect(rectPlayer.get())
|
adapter.clearRect(rectPlayer.get())
|
||||||
checkpoint('afterclear_2')
|
checkpoint('afterclear_2')
|
||||||
mapBitmapToAdapterCapped(puzzleTable, new BoundingRectangle(
|
mapBitmapToAdapterCapped(
|
||||||
- cam.x,
|
puzzleTable,
|
||||||
- cam.x + (cam.width / cam.zoom),
|
cam.rect(),
|
||||||
- cam.y,
|
adapter,
|
||||||
- cam.y + (cam.height / cam.zoom),
|
adapter.getBoundingRect(),
|
||||||
), adapter, adapter.getBoundingRect(), rectPlayer.get())
|
rectPlayer.get()
|
||||||
|
)
|
||||||
checkpoint('to_adapter_2')
|
checkpoint('to_adapter_2')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,23 @@
|
||||||
import WebSocketServer from './WebSocketServer.js'
|
import WebSocketServer from './WebSocketServer.js'
|
||||||
|
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import { createPuzzle } from './puzzle.js'
|
||||||
import config from './config.js'
|
import config from './config.js'
|
||||||
|
|
||||||
|
// desired number of tiles
|
||||||
|
// actual calculated number can be higher
|
||||||
|
const TARGET_TILES = 1000
|
||||||
|
|
||||||
|
const IMAGES = [
|
||||||
|
'/example-images/ima_86ec3fa.jpeg',
|
||||||
|
'/example-images/bleu.png',
|
||||||
|
'/example-images/saechsische_schweiz.jpg',
|
||||||
|
'/example-images/132-2048x1365.jpg',
|
||||||
|
]
|
||||||
|
const IMAGE_URL = IMAGES[0]
|
||||||
|
|
||||||
|
const games = {}
|
||||||
|
|
||||||
const port = config.http.port
|
const port = config.http.port
|
||||||
const hostname = config.http.hostname
|
const hostname = config.http.hostname
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
@ -27,8 +41,6 @@ app.use('/', (req, res, next) => {
|
||||||
})
|
})
|
||||||
app.listen(port, hostname, () => console.log(`server running on http://${hostname}:${port}`))
|
app.listen(port, hostname, () => console.log(`server running on http://${hostname}:${port}`))
|
||||||
|
|
||||||
const games = {}
|
|
||||||
|
|
||||||
const wss = new WebSocketServer(config.ws);
|
const wss = new WebSocketServer(config.ws);
|
||||||
|
|
||||||
const notify = (data) => {
|
const notify = (data) => {
|
||||||
|
|
@ -37,7 +49,7 @@ const notify = (data) => {
|
||||||
console.log('notify', data)
|
console.log('notify', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
wss.on('message', ({socket, data}) => {
|
wss.on('message', async ({socket, data}) => {
|
||||||
try {
|
try {
|
||||||
const proto = socket.protocol.split('|')
|
const proto = socket.protocol.split('|')
|
||||||
const uid = proto[0]
|
const uid = proto[0]
|
||||||
|
|
@ -46,7 +58,7 @@ wss.on('message', ({socket, data}) => {
|
||||||
switch (parsed.type) {
|
switch (parsed.type) {
|
||||||
case 'init': {
|
case 'init': {
|
||||||
// a new player (or previous player) joined
|
// a new player (or previous player) joined
|
||||||
games[gid] = games[gid] || {puzzle: null, players: {}}
|
games[gid] = games[gid] || {puzzle: await createPuzzle(TARGET_TILES, IMAGE_URL), players: {}}
|
||||||
|
|
||||||
games[gid].players[uid] = {id: uid, x: 0, y: 0, down: false}
|
games[gid].players[uid] = {id: uid, x: 0, y: 0, down: false}
|
||||||
|
|
||||||
|
|
@ -59,11 +71,6 @@ wss.on('message', ({socket, data}) => {
|
||||||
}, socket)
|
}, socket)
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
// new puzzle was created and sent to us
|
|
||||||
case 'init_puzzle': {
|
|
||||||
games[gid].puzzle = parsed.puzzle
|
|
||||||
} break;
|
|
||||||
|
|
||||||
// somebody has changed the state
|
// somebody has changed the state
|
||||||
case 'state': {
|
case 'state': {
|
||||||
for (let change of parsed.state.changes) {
|
for (let change of parsed.state.changes) {
|
||||||
|
|
|
||||||
16
server/package-lock.json
generated
16
server/package-lock.json
generated
|
|
@ -180,6 +180,14 @@
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"image-size": {
|
||||||
|
"version": "0.9.3",
|
||||||
|
"resolved": "https://npm.stroeermediabrands.de/image-size/-/image-size-0.9.3.tgz",
|
||||||
|
"integrity": "sha512-5SakFa79uhUVSjKeQE30GVzzLJ0QNzB53+I+/VD1vIesD6GP6uatWIlgU0uisFNLt1u0d6kBydp7yfk+lLJhLQ==",
|
||||||
|
"requires": {
|
||||||
|
"queue": "6.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://npm.stroeermediabrands.de/inherits/-/inherits-2.0.3.tgz",
|
"resolved": "https://npm.stroeermediabrands.de/inherits/-/inherits-2.0.3.tgz",
|
||||||
|
|
@ -265,6 +273,14 @@
|
||||||
"resolved": "https://npm.stroeermediabrands.de/qs/-/qs-6.7.0.tgz",
|
"resolved": "https://npm.stroeermediabrands.de/qs/-/qs-6.7.0.tgz",
|
||||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||||
},
|
},
|
||||||
|
"queue": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://npm.stroeermediabrands.de/queue/-/queue-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "~2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"range-parser": {
|
"range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://npm.stroeermediabrands.de/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://npm.stroeermediabrands.de/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"image-size": "^0.9.3",
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
151
server/puzzle.js
Normal file
151
server/puzzle.js
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import sizeOf from 'image-size'
|
||||||
|
import { choice } from './util.js'
|
||||||
|
|
||||||
|
// cut size of each puzzle tile in the
|
||||||
|
// final resized version of the puzzle image
|
||||||
|
const TILE_SIZE = 64
|
||||||
|
|
||||||
|
async function createPuzzle(targetTiles, image) {
|
||||||
|
const imgFile = './../game' + image
|
||||||
|
const imgUrl = './' + image
|
||||||
|
// load bitmap, to determine the original size of the image
|
||||||
|
let dim = sizeOf(imgFile)
|
||||||
|
|
||||||
|
// determine puzzle information from the bitmap
|
||||||
|
let info = determinePuzzleInfo(dim.width, dim.height, targetTiles)
|
||||||
|
|
||||||
|
let tiles = new Array(info.tiles)
|
||||||
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
|
tiles[i] = {
|
||||||
|
idx: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const shapes = determinePuzzleTileShapes(info)
|
||||||
|
|
||||||
|
// Complete puzzle object
|
||||||
|
const p = {
|
||||||
|
// tiles array
|
||||||
|
tiles: tiles.map(tile => {
|
||||||
|
return {
|
||||||
|
idx: tile.idx, // index of tile in the array
|
||||||
|
group: 0, // if grouped with other tiles
|
||||||
|
z: 0, // z index of the tile
|
||||||
|
owner: 0, // who owns the tile
|
||||||
|
// 0 = free for taking
|
||||||
|
// -1 = finished
|
||||||
|
// other values: id of player who has the tile
|
||||||
|
// 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
|
||||||
|
// TODO: scatter the tiles on the table at the beginning
|
||||||
|
pos: {
|
||||||
|
x: info.coords[tile.idx].x * info.tileSize,
|
||||||
|
y: info.coords[tile.idx].y * info.tileSize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// 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
|
||||||
|
},
|
||||||
|
// static puzzle information. stays same for complete duration of
|
||||||
|
// the game
|
||||||
|
info: {
|
||||||
|
// information that was used to create the puzzle
|
||||||
|
targetTiles: targetTiles,
|
||||||
|
imageUrl: imgUrl,
|
||||||
|
|
||||||
|
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
|
||||||
|
tiles_x: info.tiles_x, // number of tiles each row
|
||||||
|
tiles_y: info.tiles_y, // number of tiles each col
|
||||||
|
coords: info.coords, // map of tile index to its coordinates
|
||||||
|
// ( 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
function determinePuzzleTileShapes(info) {
|
||||||
|
const tabs = [-1, 1]
|
||||||
|
|
||||||
|
const shapes = new Array(info.tiles)
|
||||||
|
for (let i = 0; i < info.tiles; i++) {
|
||||||
|
shapes[i] = {
|
||||||
|
top: info.coords[i].y === 0 ? 0 : shapes[i - info.tiles_x].bottom * -1,
|
||||||
|
right: info.coords[i].x === info.tiles_x - 1 ? 0 : choice(tabs),
|
||||||
|
left: info.coords[i].x === 0 ? 0 : shapes[i - 1].right * -1,
|
||||||
|
bottom: info.coords[i].y === info.tiles_y - 1 ? 0 : choice(tabs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shapes
|
||||||
|
}
|
||||||
|
|
||||||
|
const determinePuzzleInfo = (w, h, targetTiles) => {
|
||||||
|
let tileSize = 0
|
||||||
|
let tiles = 0
|
||||||
|
do {
|
||||||
|
tileSize++
|
||||||
|
tiles = tilesFit(w, h, tileSize)
|
||||||
|
} while (tiles >= targetTiles)
|
||||||
|
tileSize--
|
||||||
|
|
||||||
|
tiles = tilesFit(w, h, tileSize)
|
||||||
|
const tiles_x = Math.round(w / tileSize)
|
||||||
|
const tiles_y = Math.round(h / tileSize)
|
||||||
|
tiles = tiles_x * tiles_y
|
||||||
|
|
||||||
|
// then resize to final TILE_SIZE (which is always the same)
|
||||||
|
tileSize = TILE_SIZE
|
||||||
|
const width = tiles_x * tileSize
|
||||||
|
const height = tiles_y * tileSize
|
||||||
|
const coords = coordsByNum({ width, height, tileSize, tiles })
|
||||||
|
|
||||||
|
const tileMarginWidth = tileSize * .5;
|
||||||
|
const tileDrawSize = Math.round(tileSize + tileMarginWidth * 2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
tileSize,
|
||||||
|
tileMarginWidth,
|
||||||
|
tileDrawSize,
|
||||||
|
tiles,
|
||||||
|
tiles_x,
|
||||||
|
tiles_y,
|
||||||
|
coords,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tilesFit = (w, h, size) => Math.floor(w / size) * Math.floor(h / size)
|
||||||
|
|
||||||
|
const coordsByNum = (puzzleInfo) => {
|
||||||
|
const w_tiles = puzzleInfo.width / puzzleInfo.tileSize
|
||||||
|
const coords = new Array(puzzleInfo.tiles)
|
||||||
|
for (let i = 0; i < puzzleInfo.tiles; i++) {
|
||||||
|
const y = Math.floor(i / w_tiles)
|
||||||
|
const x = i % w_tiles
|
||||||
|
coords[i] = { x, y }
|
||||||
|
}
|
||||||
|
return coords
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
createPuzzle
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue