add other snap mode

This commit is contained in:
Zutatensuppe 2021-06-04 09:26:37 +02:00
parent 42aaf10679
commit 2a12900614
11 changed files with 164 additions and 27 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<title>🧩 jigsaw.hyottoko.club</title>
<script type="module" crossorigin src="/assets/index.4d5f4980.js"></script>
<script type="module" crossorigin src="/assets/index.6e65c5cb.js"></script>
<link rel="modulepreload" href="/assets/vendor.684f7bc8.js">
<link rel="stylesheet" href="/assets/index.6748df9f.css">
</head>

View file

@ -30,6 +30,11 @@ var ShapeMode;
ShapeMode[ShapeMode["ANY"] = 1] = "ANY";
ShapeMode[ShapeMode["FLAT"] = 2] = "FLAT";
})(ShapeMode || (ShapeMode = {}));
var SnapMode;
(function (SnapMode) {
SnapMode[SnapMode["NORMAL"] = 0] = "NORMAL";
SnapMode[SnapMode["REAL"] = 1] = "REAL";
})(SnapMode || (SnapMode = {}));
class Rng {
constructor(seed) {
@ -173,6 +178,8 @@ function encodeGame(data) {
data.players,
data.evtInfos,
data.scoreMode || ScoreMode.FINAL,
data.shapeMode || ShapeMode.ANY,
data.snapMode || SnapMode.NORMAL,
];
}
function decodeGame(data) {
@ -186,6 +193,8 @@ function decodeGame(data) {
players: data[4],
evtInfos: data[5],
scoreMode: data[6],
shapeMode: data[7],
snapMode: data[8],
};
}
function coordByPieceIdx(info, pieceIdx) {
@ -599,6 +608,9 @@ function setImageUrl(gameId, imageUrl) {
function getScoreMode(gameId) {
return GAMES[gameId].scoreMode || ScoreMode.FINAL;
}
function getSnapMode(gameId) {
return GAMES[gameId].snapMode || SnapMode.NORMAL;
}
function isFinished(gameId) {
return getFinishedPiecesCount(gameId) === getPieceCount(gameId);
}
@ -647,6 +659,14 @@ const getPieceGroup = (gameId, tileIdx) => {
const tile = getPiece(gameId, tileIdx);
return tile.group;
};
const isCornerPiece = (gameId, tileIdx) => {
const info = GAMES[gameId].puzzle.info;
return (tileIdx === 0 // top left corner
|| tileIdx === (info.tilesX - 1) // top right corner
|| tileIdx === (info.tiles - info.tilesX) // bottom left corner
|| tileIdx === (info.tiles - 1) // bottom right corner
);
};
const getFinalPiecePos = (gameId, tileIdx) => {
const info = GAMES[gameId].puzzle.info;
const boardPos = {
@ -784,6 +804,12 @@ const movePiecesDiff = (gameId, pieceIdxs, diff) => {
moveTileDiff(gameId, pieceIdx, cappedDiff);
}
};
const isFinishedPiece = (gameId, pieceIdx) => {
return getPieceOwner(gameId, pieceIdx) === -1;
};
const getPieceOwner = (gameId, pieceIdx) => {
return getPiece(gameId, pieceIdx).owner;
};
const finishPieces = (gameId, pieceIdxs) => {
for (const pieceIdx of pieceIdxs) {
changePiece(gameId, pieceIdx, { owner: -1, z: 1 });
@ -1052,7 +1078,23 @@ function handleInput$1(gameId, playerId, input, ts, onSnap) {
// Check if the tile was dropped near the final location
const tilePos = getPiecePos(gameId, pieceIdx);
const finalPos = getFinalPiecePos(gameId, pieceIdx);
if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
let canSnapToFinal = false;
console.log(getSnapMode(gameId));
if (getSnapMode(gameId) === SnapMode.REAL) {
// only can snap to final if any of the grouped pieces are
// corner pieces
for (const pieceIdxTmp of pieceIdxs) {
if (isCornerPiece(gameId, pieceIdxTmp)) {
canSnapToFinal = true;
break;
}
}
}
else {
canSnapToFinal = true;
}
if (canSnapToFinal
&& Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
const diff = Geometry.pointSub(finalPos, tilePos);
// Snap the tile to the final destination
movePiecesDiff(gameId, pieceIdxs, diff);
@ -1095,8 +1137,13 @@ function handleInput$1(gameId, playerId, input, ts, onSnap) {
movePiecesDiff(gameId, pieceIdxs, diff);
groupTiles(gameId, tileIdx, otherTileIdx);
pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx);
if (isFinishedPiece(gameId, otherTileIdx)) {
finishPieces(gameId, pieceIdxs);
}
else {
const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs);
setPiecesZIndex(gameId, pieceIdxs, zIndex);
}
_pieceChanges(pieceIdxs);
return true;
}
@ -1687,6 +1734,8 @@ function loadGame(gameId) {
players: game.players,
evtInfos: {},
scoreMode: game.scoreMode || ScoreMode.FINAL,
shapeMode: game.shapeMode || ShapeMode.ANY,
snapMode: game.snapMode || SnapMode.NORMAL,
};
GameCommon.setGame(gameObject.id, gameObject);
}
@ -1713,6 +1762,8 @@ function persistGame(gameId) {
puzzle: game.puzzle,
players: game.players,
scoreMode: game.scoreMode,
shapeMode: game.shapeMode,
snapMode: game.snapMode,
}));
log$3.info(`[INFO] persisted game ${game.id}`);
}
@ -1724,7 +1775,7 @@ var GameStorage = {
setDirty,
};
async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode) {
async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode) {
const seed = Util.hash(gameId + ' ' + ts);
const rng = new Rng(seed);
return {
@ -1735,12 +1786,13 @@ async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shape
evtInfos: {},
scoreMode,
shapeMode,
snapMode,
};
}
async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode) {
const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode);
async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode) {
const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode);
GameLog.create(gameId);
GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode, shapeMode);
GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode, shapeMode, snapMode);
GameCommon.setGame(gameObject.id, gameObject);
GameStorage.setDirty(gameId);
}
@ -2014,7 +2066,7 @@ app.get('/api/replay-data', async (req, res) => {
let game = null;
if (offset === 0) {
// also need the game
game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL, log[0][6] || ShapeMode.NORMAL);
game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL, log[0][6] || ShapeMode.NORMAL, log[0][7] || SnapMode.NORMAL);
}
res.send({ log, game: game ? Util.encodeGame(game) : null });
});
@ -2102,7 +2154,7 @@ app.post('/api/newgame', express.json(), async (req, res) => {
const gameId = Util.uniqId();
if (!GameCommon.exists(gameId)) {
const ts = Time.timestamp();
await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode, gameSettings.shapeMode);
await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode, gameSettings.shapeMode, gameSettings.snapMode);
}
res.send({ id: gameId });
});

View file

@ -16,6 +16,7 @@ import {
PuzzleData,
PuzzleDataChange,
ScoreMode,
SnapMode,
Timestamp
} from './Types'
import Util from './Util'
@ -172,6 +173,10 @@ function getScoreMode(gameId: string): ScoreMode {
return GAMES[gameId].scoreMode || ScoreMode.FINAL
}
function getSnapMode(gameId: string): SnapMode {
return GAMES[gameId].snapMode || SnapMode.NORMAL
}
function isFinished(gameId: string): boolean {
return getFinishedPiecesCount(gameId) === getPieceCount(gameId)
}
@ -237,6 +242,16 @@ const getPieceGroup = (gameId: string, tileIdx: number): number => {
return tile.group
}
const isCornerPiece = (gameId: string, tileIdx: number): boolean => {
const info = GAMES[gameId].puzzle.info
return (
tileIdx === 0 // top left corner
|| tileIdx === (info.tilesX - 1) // top right corner
|| tileIdx === (info.tiles - info.tilesX) // bottom left corner
|| tileIdx === (info.tiles - 1) // bottom right corner
)
}
const getFinalPiecePos = (gameId: string, tileIdx: number): Point => {
const info = GAMES[gameId].puzzle.info
const boardPos = {
@ -406,6 +421,14 @@ const movePiecesDiff = (
}
}
const isFinishedPiece = (gameId: string, pieceIdx: number): boolean => {
return getPieceOwner(gameId, pieceIdx) === -1
}
const getPieceOwner = (gameId: string, pieceIdx: number): string|number => {
return getPiece(gameId, pieceIdx).owner
}
const finishPieces = (gameId: string, pieceIdxs: Array<number>): void => {
for (const pieceIdx of pieceIdxs) {
changePiece(gameId, pieceIdx, { owner: -1, z: 1 })
@ -721,7 +744,26 @@ function handleInput(
// Check if the tile was dropped near the final location
const tilePos = getPiecePos(gameId, pieceIdx)
const finalPos = getFinalPiecePos(gameId, pieceIdx)
if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
let canSnapToFinal = false
console.log(getSnapMode(gameId))
if (getSnapMode(gameId) === SnapMode.REAL) {
// only can snap to final if any of the grouped pieces are
// corner pieces
for (const pieceIdxTmp of pieceIdxs) {
if (isCornerPiece(gameId, pieceIdxTmp)) {
canSnapToFinal = true
break
}
}
} else {
canSnapToFinal = true
}
if (
canSnapToFinal
&& Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance
) {
const diff = Geometry.pointSub(finalPos, tilePos)
// Snap the tile to the final destination
movePiecesDiff(gameId, pieceIdxs, diff)
@ -774,8 +816,12 @@ function handleInput(
movePiecesDiff(gameId, pieceIdxs, diff)
groupTiles(gameId, tileIdx, otherTileIdx)
pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx)
if (isFinishedPiece(gameId, otherTileIdx)) {
finishPieces(gameId, pieceIdxs)
} else {
const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs)
setPiecesZIndex(gameId, pieceIdxs, zIndex)
}
_pieceChanges(pieceIdxs)
return true
}

View file

@ -49,6 +49,8 @@ export type EncodedGame = FixedLengthArray<[
Array<EncodedPlayer>,
Record<string, EvtInfo>,
ScoreMode,
ShapeMode,
SnapMode,
]>
export interface ReplayData {
@ -75,6 +77,7 @@ export interface Game {
evtInfos: Record<string, EvtInfo>
scoreMode?: ScoreMode
shapeMode?: ShapeMode
snapMode?: SnapMode
rng: GameRng
}
@ -93,6 +96,7 @@ export interface GameSettings {
image: Image
scoreMode: ScoreMode
shapeMode: ShapeMode
snapMode: SnapMode
}
export interface Puzzle {
@ -207,3 +211,8 @@ export enum ShapeMode {
ANY = 1,
FLAT = 2,
}
export enum SnapMode {
NORMAL = 0,
REAL = 1,
}

View file

@ -9,7 +9,9 @@ import {
PieceShape,
Player,
PuzzleInfo,
ScoreMode
ScoreMode,
ShapeMode,
SnapMode
} from './Types'
import { Point } from './Geometry'
import { Rng } from './Rng'
@ -129,6 +131,8 @@ function encodeGame(data: Game): EncodedGame {
data.players,
data.evtInfos,
data.scoreMode || ScoreMode.FINAL,
data.shapeMode || ShapeMode.ANY,
data.snapMode || SnapMode.NORMAL,
]
}
@ -143,6 +147,8 @@ function decodeGame(data: EncodedGame): Game {
players: data[4],
evtInfos: data[5],
scoreMode: data[6],
shapeMode: data[7],
snapMode: data[8],
}
}

View file

@ -32,6 +32,14 @@
<label><input type="radio" v-model="shapeMode" value="2" /> Flat (all pieces flat on all sides)</label>
</td>
</tr>
<tr>
<td><label>Snapping: </label></td>
<td>
<label><input type="radio" v-model="snapMode" value="0" /> Normal (pieces snap to final destination and to each other)</label>
<br />
<label><input type="radio" v-model="snapMode" value="1" /> Real (pieces snap only to corners, already snapped pieces and to each other)</label>
</td>
</tr>
</table>
</div>
@ -46,7 +54,7 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { GameSettings, ScoreMode, ShapeMode } from './../../common/Types'
import { GameSettings, ScoreMode, ShapeMode, SnapMode } from './../../common/Types'
import ResponsiveImage from './ResponsiveImage.vue'
export default defineComponent({
@ -69,6 +77,7 @@ export default defineComponent({
tiles: 1000,
scoreMode: ScoreMode.ANY,
shapeMode: ShapeMode.NORMAL,
snapMode: SnapMode.NORMAL,
}
},
methods: {
@ -78,6 +87,7 @@ export default defineComponent({
image: this.image,
scoreMode: this.scoreModeInt,
shapeMode: this.shapeModeInt,
snapMode: this.snapModeInt,
} as GameSettings)
},
},
@ -99,6 +109,9 @@ export default defineComponent({
shapeModeInt (): number {
return parseInt(`${this.shapeMode}`, 10)
},
snapModeInt (): number {
return parseInt(`${this.snapMode}`, 10)
},
tilesInt (): number {
return parseInt(`${this.tiles}`, 10)
},

View file

@ -1,5 +1,5 @@
import GameCommon from './../common/GameCommon'
import { Change, Game, Input, ScoreMode, ShapeMode, Timestamp } from './../common/Types'
import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode, Timestamp } from './../common/Types'
import Util, { logger } from './../common/Util'
import { Rng } from './../common/Rng'
import GameLog from './GameLog'
@ -15,7 +15,8 @@ async function createGameObject(
image: PuzzleCreationImageInfo,
ts: Timestamp,
scoreMode: ScoreMode,
shapeMode: ShapeMode
shapeMode: ShapeMode,
snapMode: SnapMode
): Promise<Game> {
const seed = Util.hash(gameId + ' ' + ts)
const rng = new Rng(seed)
@ -27,6 +28,7 @@ async function createGameObject(
evtInfos: {},
scoreMode,
shapeMode,
snapMode,
}
}
@ -36,7 +38,8 @@ async function createGame(
image: PuzzleCreationImageInfo,
ts: Timestamp,
scoreMode: ScoreMode,
shapeMode: ShapeMode
shapeMode: ShapeMode,
snapMode: SnapMode
): Promise<void> {
const gameObject = await createGameObject(
gameId,
@ -44,7 +47,8 @@ async function createGame(
image,
ts,
scoreMode,
shapeMode
shapeMode,
snapMode
)
GameLog.create(gameId)
@ -56,7 +60,8 @@ async function createGame(
image,
ts,
scoreMode,
shapeMode
shapeMode,
snapMode
)
GameCommon.setGame(gameObject.id, gameObject)

View file

@ -1,6 +1,6 @@
import fs from 'fs'
import GameCommon from './../common/GameCommon'
import { Piece, ScoreMode } from './../common/Types'
import { Game, Piece, ScoreMode, ShapeMode, SnapMode } from './../common/Types'
import Util, { logger } from './../common/Util'
import { Rng } from './../common/Rng'
import { DATA_DIR } from './Dirs'
@ -49,7 +49,7 @@ function loadGame(gameId: string): void {
if (!Array.isArray(game.players)) {
game.players = Object.values(game.players)
}
const gameObject = {
const gameObject: Game = {
id: game.id,
rng: {
type: game.rng ? game.rng.type : '_fake_',
@ -59,6 +59,8 @@ function loadGame(gameId: string): void {
players: game.players,
evtInfos: {},
scoreMode: game.scoreMode || ScoreMode.FINAL,
shapeMode: game.shapeMode || ShapeMode.ANY,
snapMode: game.snapMode || SnapMode.NORMAL,
}
GameCommon.setGame(gameObject.id, gameObject)
}
@ -88,6 +90,8 @@ function persistGame(gameId: string): void {
puzzle: game.puzzle,
players: game.players,
scoreMode: game.scoreMode,
shapeMode: game.shapeMode,
snapMode: game.snapMode,
}))
log.info(`[INFO] persisted game ${game.id}`)
}

View file

@ -19,7 +19,7 @@ import {
UPLOAD_DIR,
} from './Dirs'
import GameCommon from '../common/GameCommon'
import { ServerEvent, Game as GameType, GameSettings, ScoreMode, ShapeMode } from '../common/Types'
import { ServerEvent, Game as GameType, GameSettings, ScoreMode, ShapeMode, SnapMode } from '../common/Types'
import GameStorage from './GameStorage'
import Db from './Db'
@ -90,7 +90,8 @@ app.get('/api/replay-data', async (req, res): Promise<void> => {
log[0][3],
log[0][4],
log[0][5] || ScoreMode.FINAL,
log[0][6] || ShapeMode.NORMAL
log[0][6] || ShapeMode.NORMAL,
log[0][7] || SnapMode.NORMAL,
)
}
res.send({ log, game: game ? Util.encodeGame(game) : null })
@ -203,7 +204,8 @@ app.post('/api/newgame', express.json(), async (req, res): Promise<void> => {
gameSettings.image,
ts,
gameSettings.scoreMode,
gameSettings.shapeMode
gameSettings.shapeMode,
gameSettings.snapMode,
)
}
res.send({ id: gameId })