From 96100a157fd9fa5b4833d27789c134a2f82676e0 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 10:25:33 +0900 Subject: [PATCH 01/14] =?UTF-8?q?[feat]=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EC=98=A8=EB=A6=AC=20=EB=9E=98=ED=8D=BC=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/ClientOnly.jsx | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/common/ClientOnly.jsx diff --git a/src/common/ClientOnly.jsx b/src/common/ClientOnly.jsx new file mode 100644 index 00000000..da150a47 --- /dev/null +++ b/src/common/ClientOnly.jsx @@ -0,0 +1,33 @@ +import { useSyncExternalStore, useEffect } from "react"; + +const mountedStore = { + mounted: false, + listeners: new Set(), + mount() { + this.mounted = true; + this.listeners.forEach( listener=>listener() ); + } + subscribe(listener) { + this.listeners.add(listener); + return ()=>this.listeners.delete(listener); + } + getSnapshot() { + return mounted; + } +} + + +/** + * react 클라이언트 only 래퍼 입니다. + * 이 래퍼 컴포넌트는 클라이언트에서만 필요한 동작이 필요할 때, SSR시와 하이드레이션시 반환한 함수가 + * 동일한 폴백 엘리먼트를 렌더링하도록 보장합니다. + */ +export default function ClientOnly({ children, fallback }) { + const mounted = useSyncExternalStore(mountedStore.subscribe, mountedStore.getSnapshot, false); + useEffect(() => { + mountedStore.mount(); + }, []); + + if (!init) return fallback; + return children; +} From b918c93e2e33d81e0bfedddd3c850f3428f5947c Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 10:32:58 +0900 Subject: [PATCH 02/14] =?UTF-8?q?[fix]=20ClientOnly=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=AC=B8=EB=B2=95=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/ClientOnly.jsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/common/ClientOnly.jsx b/src/common/ClientOnly.jsx index da150a47..3f452da6 100644 --- a/src/common/ClientOnly.jsx +++ b/src/common/ClientOnly.jsx @@ -4,30 +4,29 @@ const mountedStore = { mounted: false, listeners: new Set(), mount() { - this.mounted = true; - this.listeners.forEach( listener=>listener() ); - } + mountedStore.mounted = true; + mountedStore.listeners.forEach( listener=>listener() ); + }, subscribe(listener) { - this.listeners.add(listener); - return ()=>this.listeners.delete(listener); - } + mountedStore.listeners.add(listener); + return ()=>mountedStore.listeners.delete(listener); + }, getSnapshot() { - return mounted; + return mountedStore.mounted; } } - /** * react 클라이언트 only 래퍼 입니다. * 이 래퍼 컴포넌트는 클라이언트에서만 필요한 동작이 필요할 때, SSR시와 하이드레이션시 반환한 함수가 * 동일한 폴백 엘리먼트를 렌더링하도록 보장합니다. */ export default function ClientOnly({ children, fallback }) { - const mounted = useSyncExternalStore(mountedStore.subscribe, mountedStore.getSnapshot, false); + const mounted = useSyncExternalStore(mountedStore.subscribe, mountedStore.getSnapshot, ()=>false); useEffect(() => { mountedStore.mount(); }, []); - if (!init) return fallback; + if (!mounted) return fallback; return children; } From c56455d54c0ba37fca3b06091f37f45ad013d222 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 10:37:27 +0900 Subject: [PATCH 03/14] =?UTF-8?q?[refactor]=20Suspense=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=20ClientOnly=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/Suspense.jsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/common/Suspense.jsx b/src/common/Suspense.jsx index d3d7bb3d..59a1f7d0 100644 --- a/src/common/Suspense.jsx +++ b/src/common/Suspense.jsx @@ -1,4 +1,5 @@ -import { Suspense as ReactSuspense, useState, useEffect } from "react"; +import { Suspense as ReactSuspense } from "react"; +import ClientOnly from "./ClientOnly.jsx"; /** * react 의 래퍼 컴포넌트입니다. @@ -8,11 +9,7 @@ import { Suspense as ReactSuspense, useState, useEffect } from "react"; * 출처 : https://toss.tech/article/faster-initial-rendering */ export default function Suspense({ children, fallback }) { - const [init, setInit] = useState(false); - useEffect(() => { - setInit(true); - }, []); - - if (!init) return fallback; - return {children}; + return + {children} + ; } From c302d94b19cdc51b01b2ae82c90e8ee9015428de Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 14:16:11 +0900 Subject: [PATCH 04/14] =?UTF-8?q?[feat]=20=EB=AC=B8=EC=9E=90=EC=97=B4=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EA=B0=81=20piece=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/constants.js | 3 ++ src/interactions/v2l/utils.js | 63 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/interactions/v2l/constants.js create mode 100644 src/interactions/v2l/utils.js diff --git a/src/interactions/v2l/constants.js b/src/interactions/v2l/constants.js new file mode 100644 index 00000000..05041ee9 --- /dev/null +++ b/src/interactions/v2l/constants.js @@ -0,0 +1,3 @@ +export const LINEAR = Symbol("linear"); +export const CURVED = Symbol("curved"); +export const ANY = Symbol("any"); \ No newline at end of file diff --git a/src/interactions/v2l/utils.js b/src/interactions/v2l/utils.js new file mode 100644 index 00000000..403c4a2f --- /dev/null +++ b/src/interactions/v2l/utils.js @@ -0,0 +1,63 @@ +import { LINEAR, CURVED, ANY } from "./constants.js"; + +// ─│┌┐┘└ + +class Piece +{ + constructor(shapeChar) + { + switch(shapeChar) + { + case "─": this.type=LINEAR; this.rotate=0; break; + case "│": this.type=LINEAR; this.rotate=1; break; + case "┌": this.type=CURVED; this.rotate=0; break; + case "┐": this.type=CURVED; this.rotate=1; break; + case "┘": this.type=CURVED; this.rotate=2; break; + case "└": this.type=CURVED; this.rotate=3; break; + } + this.symbol = shapeChar; + } + rotated() + { + const newPiece = new Piece(this.symbol); + newPiece.rotate = this.rotate + 1; + return newPiece; + } + isCorrect(answer) + { + if(answer === ANY) return true; + if(this.type === LINEAR) return this.rotate % 2 === answer; + return this.rotate % 4 === answer; + } + fixedRotated() + { + const newPiece = new Piece(this.symbol); + if(this.type === LINEAR) newPiece.rotate = this.rotate % 2; + else newPiece.rotate = this.rotate % 4; + return newPiece; + } +} +function generatePiece(shapeString) +{ + const rawString = [...shapeString.replace(/\s+/gm, "")]; + return rawString.map( c=>new Piece(c) ); +} + +function generateAnswer(shapeString) +{ + const rawString = [...shapeString.replace(/\s+/gm, "")]; + return rawString.map( c=>{ + switch(c) + { + case "─": return 0; + case "│": return 1; + case "┌": return 0; + case "┐": return 1; + case "┘": return 2; + case "└": return 3; + default: return ANY; + } + } ); +} + +export default generatePiece; \ No newline at end of file From ac9ebed9ad716f9b7d222e95cb88e37dc73c9441 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 14:34:25 +0900 Subject: [PATCH 05/14] =?UTF-8?q?[feat]=20=ED=8D=BC=EC=A6=90=20=EA=B0=81?= =?UTF-8?q?=20=EC=A1=B0=EA=B0=81=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=A0=84=ED=95=98=EB=8A=94=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/Puzzle.jsx | 48 ++++++++++++++++++ src/interactions/v2l/PuzzlePiece.jsx | 28 ++++++++++ src/interactions/v2l/assets/car@1x.png | Bin 0 -> 3992 bytes src/interactions/v2l/assets/car@2x.png | Bin 0 -> 8824 bytes src/interactions/v2l/assets/pan.svg | 6 +++ .../v2l/assets/panContainer@1x.png | Bin 0 -> 2543 bytes .../v2l/assets/panContainer@2x.png | Bin 0 -> 4877 bytes src/interactions/v2l/index.jsx | 24 +++++++++ src/interactions/v2l/utils.js | 18 +++---- 9 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 src/interactions/v2l/Puzzle.jsx create mode 100644 src/interactions/v2l/PuzzlePiece.jsx create mode 100644 src/interactions/v2l/assets/car@1x.png create mode 100644 src/interactions/v2l/assets/car@2x.png create mode 100644 src/interactions/v2l/assets/pan.svg create mode 100644 src/interactions/v2l/assets/panContainer@1x.png create mode 100644 src/interactions/v2l/assets/panContainer@2x.png create mode 100644 src/interactions/v2l/index.jsx diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx new file mode 100644 index 00000000..df91d4e0 --- /dev/null +++ b/src/interactions/v2l/Puzzle.jsx @@ -0,0 +1,48 @@ +import { useState } from "react"; +import { generatePiece, generateAnswer } from "./utils.js"; +import PuzzlePiece from "./PuzzlePiece.jsx"; + +// ─│┌┐┘└ + +function Puzzle() +{ + const [ answer, setAnswer ] = useState( + generateAnswer(` + ─┐. + .│. + .└─` + ) + ); + const [ piece, setPiece ] = useState( + generatePiece(` + │┘─ + ─││ + │┘│` + ) + ); + + return
+
+ { piece.map( (shape, i)=>{ + const onClick = ()=>{ + setPiece( board=>{ + const newBoard = [...board]; + newBoard[i] = board[i].rotated(); + return newBoard; + } ); + } + const fixRotate = ()=>{ + setPiece( board=>{ + const newBoard = [...board]; + newBoard[i] = board[i].fixedRotated(); + return newBoard; + } ); + } + + return ; + } ) } +
+
+} + +export default Puzzle; \ No newline at end of file diff --git a/src/interactions/v2l/PuzzlePiece.jsx b/src/interactions/v2l/PuzzlePiece.jsx new file mode 100644 index 00000000..bf4b057f --- /dev/null +++ b/src/interactions/v2l/PuzzlePiece.jsx @@ -0,0 +1,28 @@ +import { useState } from "react"; +import { LINEAR } from "./constants.js"; + +function PuzzlePiece({shape, onClick, fixRotate}) +{ + const [fixing, setFixing] = useState(false); + const style = { + transform: `rotate( ${shape.rotate*90}deg)` + }; + + return
{ + onClick(); + setFixing(false); + }} + onTransitionEnd={()=>{ + if(shape.rotate < 4) return; + setFixing(true); + fixRotate(); + }} + > + {shape.type === LINEAR ? "-" : "┌"} +
+} + +export default PuzzlePiece; \ No newline at end of file diff --git a/src/interactions/v2l/assets/car@1x.png b/src/interactions/v2l/assets/car@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6738aa1c3a35de556cdf29adb68e466c81b028 GIT binary patch literal 3992 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 + + + + + diff --git a/src/interactions/v2l/assets/panContainer@1x.png b/src/interactions/v2l/assets/panContainer@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..e52502089c65707e108535cde18f83ca73efc8ce GIT binary patch literal 2543 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 literal 0 HcmV?d00001 diff --git a/src/interactions/v2l/index.jsx b/src/interactions/v2l/index.jsx new file mode 100644 index 00000000..9f687d92 --- /dev/null +++ b/src/interactions/v2l/index.jsx @@ -0,0 +1,24 @@ +import ClientOnly from "@/common/ClientOnly.jsx"; +import InteractionDescription from "../InteractionDescription.jsx"; +import Puzzle from "./Puzzle.jsx"; +//import PuzzleSkeleton from "./PuzzleSkeleton.jsx"; + +function V2LInteraction({ interactCallback, $ref }) { + return ( +
+ +
+ 스켈레톤 그릴 예정
}> + + + +
+ ); +} + +export default V2LInteraction; diff --git a/src/interactions/v2l/utils.js b/src/interactions/v2l/utils.js index 403c4a2f..4ba60a0d 100644 --- a/src/interactions/v2l/utils.js +++ b/src/interactions/v2l/utils.js @@ -2,7 +2,7 @@ import { LINEAR, CURVED, ANY } from "./constants.js"; // ─│┌┐┘└ -class Piece +class PieceData { constructor(shapeChar) { @@ -19,7 +19,7 @@ class Piece } rotated() { - const newPiece = new Piece(this.symbol); + const newPiece = new PieceData(this.symbol); newPiece.rotate = this.rotate + 1; return newPiece; } @@ -31,19 +31,19 @@ class Piece } fixedRotated() { - const newPiece = new Piece(this.symbol); - if(this.type === LINEAR) newPiece.rotate = this.rotate % 2; - else newPiece.rotate = this.rotate % 4; + const newPiece = new PieceData(this.symbol); + newPiece.rotate = this.rotate % 4; return newPiece; } } -function generatePiece(shapeString) + +export function generatePiece(shapeString) { const rawString = [...shapeString.replace(/\s+/gm, "")]; - return rawString.map( c=>new Piece(c) ); + return rawString.map( c=>new PieceData(c) ); } -function generateAnswer(shapeString) +export function generateAnswer(shapeString) { const rawString = [...shapeString.replace(/\s+/gm, "")]; return rawString.map( c=>{ @@ -59,5 +59,3 @@ function generateAnswer(shapeString) } } ); } - -export default generatePiece; \ No newline at end of file From 8fe476e8b9c6fdcc05efc049c67ef513ceb9a8fb Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 14:48:51 +0900 Subject: [PATCH 06/14] =?UTF-8?q?[design]=20=ED=8D=BC=EC=A6=90=EC=A1=B0?= =?UTF-8?q?=EA=B0=81=20=EC=84=A0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/Puzzle.jsx | 2 +- src/interactions/v2l/PuzzlePiece.jsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx index df91d4e0..cfea705f 100644 --- a/src/interactions/v2l/Puzzle.jsx +++ b/src/interactions/v2l/Puzzle.jsx @@ -22,7 +22,7 @@ function Puzzle() ); return
-
+
{ piece.map( (shape, i)=>{ const onClick = ()=>{ setPiece( board=>{ diff --git a/src/interactions/v2l/PuzzlePiece.jsx b/src/interactions/v2l/PuzzlePiece.jsx index bf4b057f..a394c219 100644 --- a/src/interactions/v2l/PuzzlePiece.jsx +++ b/src/interactions/v2l/PuzzlePiece.jsx @@ -21,7 +21,10 @@ function PuzzlePiece({shape, onClick, fixRotate}) fixRotate(); }} > - {shape.type === LINEAR ? "-" : "┌"} + + +
} From 0b8b34363a3b17dc3ff5d84593ec289bb39ecbe8 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 15:26:11 +0900 Subject: [PATCH 07/14] =?UTF-8?q?[design]=20=ED=8D=BC=EC=A6=90=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EB=A5=BC=20=EB=B0=98=EC=9D=91=ED=98=95=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/Puzzle.jsx | 18 +++++++++++++++-- src/interactions/v2l/PuzzlePiece.jsx | 2 +- src/interactions/v2l/assets/car@1x.png | Bin 3992 -> 5084 bytes src/interactions/v2l/assets/car@2x.png | Bin 8824 -> 11336 bytes .../v2l/assets/panContainer@1x.png | Bin 2543 -> 2134 bytes .../v2l/assets/panContainer@2x.png | Bin 4877 -> 4609 bytes src/interactions/v2l/index.jsx | 5 +++-- src/interactions/v2l/style.module.css | 19 ++++++++++++++++++ 8 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/interactions/v2l/style.module.css diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx index cfea705f..1b87f829 100644 --- a/src/interactions/v2l/Puzzle.jsx +++ b/src/interactions/v2l/Puzzle.jsx @@ -1,6 +1,11 @@ import { useState } from "react"; import { generatePiece, generateAnswer } from "./utils.js"; import PuzzlePiece from "./PuzzlePiece.jsx"; +import car1x from "./assets/car@1x.png"; +import car2x from "./assets/car@2x.png"; +import panContainer1x from "./assets/panContainer@1x.png"; +import panContainer2x from "./assets/panContainer@1x.png"; +import pan from "./assets/pan.svg"; // ─│┌┐┘└ @@ -21,8 +26,12 @@ function Puzzle() ) ); - return
-
+ return
+
+ start position +
+
+
{ piece.map( (shape, i)=>{ const onClick = ()=>{ setPiece( board=>{ @@ -42,6 +51,11 @@ function Puzzle() return ; } ) }
+
+
+ start position +
+
} diff --git a/src/interactions/v2l/PuzzlePiece.jsx b/src/interactions/v2l/PuzzlePiece.jsx index a394c219..a2651274 100644 --- a/src/interactions/v2l/PuzzlePiece.jsx +++ b/src/interactions/v2l/PuzzlePiece.jsx @@ -9,7 +9,7 @@ function PuzzlePiece({shape, onClick, fixRotate}) }; return
{ onClick(); diff --git a/src/interactions/v2l/assets/car@1x.png b/src/interactions/v2l/assets/car@1x.png index 6e6738aa1c3a35de556cdf29adb68e466c81b028..a77bd9d6e87dbb5bd948428bae0ccbb5bde4639f 100644 GIT binary patch delta 5080 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 u7$anbB#@5$9UZ+< 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 diff --git a/src/interactions/v2l/assets/car@2x.png b/src/interactions/v2l/assets/car@2x.png index 306d6e009536a9b77d827ac8b398b7feff2a3ef7..4c5e1cbe6357f99716c3163225930e69303b9bfd 100644 GIT binary patch literal 11336 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 diff --git a/src/interactions/v2l/assets/panContainer@2x.png b/src/interactions/v2l/assets/panContainer@2x.png index c6b2f5fed6275ebf3b5069e58d1a08eb06966ab9..37c0d43460fd4540b5f74b02cbdbb17eec164ac4 100644 GIT binary patch literal 4609 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 diff --git a/src/interactions/v2l/index.jsx b/src/interactions/v2l/index.jsx index 9f687d92..44d6c75b 100644 --- a/src/interactions/v2l/index.jsx +++ b/src/interactions/v2l/index.jsx @@ -1,6 +1,7 @@ import ClientOnly from "@/common/ClientOnly.jsx"; import InteractionDescription from "../InteractionDescription.jsx"; import Puzzle from "./Puzzle.jsx"; +import style from "./style.module.css"; //import PuzzleSkeleton from "./PuzzleSkeleton.jsx"; function V2LInteraction({ interactCallback, $ref }) { @@ -10,9 +11,9 @@ function V2LInteraction({ interactCallback, $ref }) { order="4" title="언제 어디서나, 편리하게" description="The new IONIQ 5로 전자기기를 작동시킬 수 있을까요?" - directive="타일을 클릭하여 The new IONIQ 5로 선풍기를 작동시켜보세요//!" + directive="타일을 클릭하여 The new IONIQ 5로 선풍기를 작동시켜보세요!" /> -
+
스켈레톤 그릴 예정
}> diff --git a/src/interactions/v2l/style.module.css b/src/interactions/v2l/style.module.css new file mode 100644 index 00000000..6a4b2a49 --- /dev/null +++ b/src/interactions/v2l/style.module.css @@ -0,0 +1,19 @@ +.container { + top: max( var(--top-area, 11rem), calc( 60% - 11.5rem ) ); + transform: scale(0.7); + --top-area: 11rem; +} + +@media (min-width: 768px) { + .container { + --top-area: 12rem; + transform: scale(0.9); + } +} + +@media (min-width: 1024px) { + .container { + --top-area: 15rem; + transform: scale(1); + } +} \ No newline at end of file From fe00da91fd69ff9a9bb89386a46fd3c618a64dbe Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 15:40:25 +0900 Subject: [PATCH 08/14] =?UTF-8?q?[refactor]=20=EB=A7=A4=EC=9A=B0=20?= =?UTF-8?q?=EB=B3=B5=EC=9E=A1=ED=95=9C=20tailwind=20=EB=B0=98=EC=9D=91?= =?UTF-8?q?=ED=98=95=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A5=BC=20=EB=B3=84?= =?UTF-8?q?=EB=8F=84=EC=9D=98=20css=20module=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fastCharge/batteryStyle.module.css | 12 --- src/interactions/univasalIsland/Phone.jsx | 9 +-- src/interactions/univasalIsland/index.jsx | 28 ++----- .../univasalIsland/style.module.css | 75 +++++++++++++++++++ 4 files changed, 84 insertions(+), 40 deletions(-) create mode 100644 src/interactions/univasalIsland/style.module.css diff --git a/src/interactions/fastCharge/batteryStyle.module.css b/src/interactions/fastCharge/batteryStyle.module.css index 2417d3e9..da40b385 100644 --- a/src/interactions/fastCharge/batteryStyle.module.css +++ b/src/interactions/fastCharge/batteryStyle.module.css @@ -1,15 +1,3 @@ -.hull { - --bar-scale: var(--progress, 1); -} - -/* -768px 미만 : 48px ~ 256px -768px 이상 : 66px ~ 352px (명세에 나온 80px ~ 410px과 실제 산출된 디자인의 width가 다름) - -8px ~ 216px - -*/ - .left { width: 1.5rem; transition: background-color 0.3s; diff --git a/src/interactions/univasalIsland/Phone.jsx b/src/interactions/univasalIsland/Phone.jsx index 39e13572..996820dc 100644 --- a/src/interactions/univasalIsland/Phone.jsx +++ b/src/interactions/univasalIsland/Phone.jsx @@ -1,10 +1,7 @@ +import style from "./style.module.css"; + function Phone({ dynamicStyle, onPointerDown, isSnapped }) { - const staticStyle = `absolute flex justify-center items-center - left-[541px] top-[293px] w-[54px] h-[97px] - lg:left-[528px] lg:top-[185px] lg:w-[66px] lg:h-[118px] - xl:left-[516px] xl:top-[75px] xl:w-[77px] xl:h-[140px] - touch-none - `; + const staticStyle = `absolute flex justify-center items-center ${style.phone} cursor-pointer touch-none`; const phoneScreenFill = isSnapped ? "fill-green-700" : "fill-neutral-900"; const lightningOpacity = isSnapped ? "opacity-100" : "opacity-0"; diff --git a/src/interactions/univasalIsland/index.jsx b/src/interactions/univasalIsland/index.jsx index 03ceaa77..b637c0ed 100644 --- a/src/interactions/univasalIsland/index.jsx +++ b/src/interactions/univasalIsland/index.jsx @@ -2,6 +2,7 @@ import { useImperativeHandle } from "react"; import InteractionDescription from "../InteractionDescription.jsx"; import Phone from "./Phone.jsx"; import useIslandDrag from "./useIslandDrag.js"; +import style from "./style.module.css"; import seat from "./assets/seat.png"; import univasalIsland1x from "./assets/univasalIsland@1x.png"; @@ -21,26 +22,9 @@ function UnivasalIslandInteraction({ interactCallback, $ref }) { useImperativeHandle($ref, () => ({ reset }), [reset]); - const seatHullStyle = `absolute w-[1200px] h-[800px] - bottom-[min(calc(100%-800px),-140px)] - lg:bottom-[min(calc(100%-900px),-170px)] - xl:bottom-[min(calc(100%-1000px),-200px)] - flex justify-center items-end select-none`; - - const seatStyle = `w-[317.44px] h-[501.88px] - lg:w-[385.46px] lg:h-[610.64px] - xl:w-[453.48px] xl:h-[718.4px]`; - - const univasalIslandStaticStyle = `w-[158.2px] h-[546px] - lg:w-[192.1px] lg:h-[663px] - xl:w-[226px] xl:h-[780px] - flex flex-col gap-2 cursor-pointer touch-none`; - - const snapAreaStyle = `absolute scale-50 - left-[21px] top-[40px] w-[54px] h-[97px] - lg:left-[25px] lg:top-[49px] lg:w-[66px] lg:h-[118px] - xl:left-[30px] xl:top-[56px] xl:w-[77px] xl:h-[140px] - `; + const seatHullStyle = `absolute w-[1200px] h-[800px] ${style.hull} flex justify-center items-end select-none`; + const univasalIslandStaticStyle = `${style.island} flex flex-col gap-2 cursor-pointer touch-none`; + const snapAreaStyle = `absolute scale-50 ${style.snap}`; return (
@@ -52,7 +36,7 @@ function UnivasalIslandInteraction({ interactCallback, $ref }) { />
left seat
right seat Date: Tue, 30 Jul 2024 16:17:45 +0900 Subject: [PATCH 09/14] =?UTF-8?q?[feat]=20=EC=A0=95=EB=8B=B5=20=EC=8B=9C?= =?UTF-8?q?=20=EC=84=A0=ED=92=8D=EA=B8=B0=EA=B0=80=20=EB=8F=8C=EC=95=84?= =?UTF-8?q?=EA=B0=80=EB=8A=94=20=ED=9A=A8=EA=B3=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/Puzzle.jsx | 16 +++++++++++++--- src/interactions/v2l/style.module.css | 13 +++++++++++++ src/interactions/v2l/utils.js | 5 +++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx index 1b87f829..7bd163e5 100644 --- a/src/interactions/v2l/Puzzle.jsx +++ b/src/interactions/v2l/Puzzle.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; -import { generatePiece, generateAnswer } from "./utils.js"; +import { generatePiece, generateAnswer, checkPuzzle } from "./utils.js"; import PuzzlePiece from "./PuzzlePiece.jsx"; +import style from "./style.module.css"; import car1x from "./assets/car@1x.png"; import car2x from "./assets/car@2x.png"; import panContainer1x from "./assets/panContainer@1x.png"; @@ -26,12 +27,17 @@ function Puzzle() ) ); + const isCorrect = checkPuzzle(piece, answer); + return
start position
+ + +
-
+
{ piece.map( (shape, i)=>{ const onClick = ()=>{ setPiece( board=>{ @@ -52,8 +58,12 @@ function Puzzle() } ) }
-
+
+ + + start position +
diff --git a/src/interactions/v2l/style.module.css b/src/interactions/v2l/style.module.css index 6a4b2a49..c97ac8ad 100644 --- a/src/interactions/v2l/style.module.css +++ b/src/interactions/v2l/style.module.css @@ -4,6 +4,10 @@ --top-area: 11rem; } +.rotate { + animation: spin 1s linear infinite; +} + @media (min-width: 768px) { .container { --top-area: 12rem; @@ -16,4 +20,13 @@ --top-area: 15rem; transform: scale(1); } +} + +@keyframes spin{ + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } \ No newline at end of file diff --git a/src/interactions/v2l/utils.js b/src/interactions/v2l/utils.js index 4ba60a0d..e742499a 100644 --- a/src/interactions/v2l/utils.js +++ b/src/interactions/v2l/utils.js @@ -59,3 +59,8 @@ export function generateAnswer(shapeString) } } ); } + +export function checkPuzzle(pieces, answer) +{ + return pieces.every( (piece, i)=>piece.isCorrect(answer[i]) ); +} From 4aa9db8303df681d1c73b5ff145932a4a299706e Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 16:22:28 +0900 Subject: [PATCH 10/14] =?UTF-8?q?[feat]=20=EB=A6=AC=EC=85=8B=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/Puzzle.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx index 7bd163e5..7c7130eb 100644 --- a/src/interactions/v2l/Puzzle.jsx +++ b/src/interactions/v2l/Puzzle.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useImperativeHandle } from "react"; import { generatePiece, generateAnswer, checkPuzzle } from "./utils.js"; import PuzzlePiece from "./PuzzlePiece.jsx"; import style from "./style.module.css"; @@ -10,7 +10,7 @@ import pan from "./assets/pan.svg"; // ─│┌┐┘└ -function Puzzle() +function Puzzle({$ref}) { const [ answer, setAnswer ] = useState( generateAnswer(` @@ -27,6 +27,12 @@ function Puzzle() ) ); + useImperativeHandle( $ref, ()=>({ + reset() { + setPiece(generatePiece(`│┘──│││┘│`)); + } + }), [] ); + const isCorrect = checkPuzzle(piece, answer); return
From bbbb5b77faf58195cb1e25844562bc6a658af598 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 16:43:27 +0900 Subject: [PATCH 11/14] =?UTF-8?q?[feat]=20=EB=9E=9C=EB=8D=A4=ED=95=9C=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=83=9D=EC=84=B1=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/generateRandom.js | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/interactions/v2l/generateRandom.js diff --git a/src/interactions/v2l/generateRandom.js b/src/interactions/v2l/generateRandom.js new file mode 100644 index 00000000..428c70b3 --- /dev/null +++ b/src/interactions/v2l/generateRandom.js @@ -0,0 +1,46 @@ +const dir = [ [1,0], [0,1], [-1,0], [0,-1] ]; + +function randInt(min, max) +{ + return Math.floor( Math.random() * (max-min) ) + min; +} + +function generateRandomPath(width, height) +{ + let traced = Array.from({length: width}, ()=>new Array(height).fill(false)); + const stack = []; + let cursor = [0,0]; + + function getNextCursorHubo([x,y]) + { + return dir + .map( ([dx, dy])=>[x+dx, y+dy] ) + .filter( ([tx, ty])=>{ + if(tx < 0 || ty < 0) return false; + if(tx >= width || ty >= height) return false; + if(traced[tx][ty] ) return false; + return true; + } ); + } + + while(cursor[0] !== width-1 || cursor[1] !== height-1) + { + traced[cursor[0]][cursor[1]] = true; + let hubo = getNextCursorHubo(cursor); + // backtracking + if(hubo.length === 0) + { + while(stack.length > 0) + { + let backtrackedCursor = stack.pop(); + hubo = getNextCursorHubo(backtrackedCursor); + if(hubo.length !== 0) break; + } + } + stack.push(cursor); + cursor = hubo[randInt(0,hubo.length)]; + } + stack.push(cursor); + + return stack; +} From 39d10175cff2a5139030ccaa89664b4549e78a9f Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 17:26:49 +0900 Subject: [PATCH 12/14] =?UTF-8?q?[feat]=20=EB=9E=9C=EB=8D=A4=ED=95=9C=20?= =?UTF-8?q?=EC=A0=95=EB=8B=B5=EA=B3=BC=20=EB=A7=90=ED=8C=90=EC=9D=84=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/generateRandom.js | 71 +++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/src/interactions/v2l/generateRandom.js b/src/interactions/v2l/generateRandom.js index 428c70b3..621733f9 100644 --- a/src/interactions/v2l/generateRandom.js +++ b/src/interactions/v2l/generateRandom.js @@ -1,3 +1,5 @@ +import { generatePiece, generateAnswer } from "./utils.js"; + const dir = [ [1,0], [0,1], [-1,0], [0,-1] ]; function randInt(min, max) @@ -32,15 +34,80 @@ function generateRandomPath(width, height) { while(stack.length > 0) { - let backtrackedCursor = stack.pop(); + let backtrackedCursor = stack[stack.length-1]; hubo = getNextCursorHubo(backtrackedCursor); if(hubo.length !== 0) break; + stack.pop(); } } - stack.push(cursor); + else stack.push(cursor); cursor = hubo[randInt(0,hubo.length)]; } stack.push(cursor); return stack; } + +function getDirection(base, target) +{ + let dx = target[0] - base[0]; + let dy = target[1] - base[1]; + + if(dx === 0 && dy === 1) return 0b0001; // down + if(dx === 1 && dy === 0) return 0b0010; // right + if(dx === -1 && dy === 0) return 0b0100; // left + if(dx === 0 && dy === -1) return 0b1000; // up + return 0b0000; +} + +// ─│┌┐┘└ +function getShapeChar(before, after) +{ + const code = before | after; + switch(code) + { + case 0b1100: return "┘"; + case 0b1010: return "└"; + case 0b1001: return "│"; + case 0b0110: return "─"; + case 0b0101: return "┐"; + case 0b0011: return "┌"; + default: return "."; + } +} + +function generateRandomPuzzle() +{ + const WIDTH = 3; + const HEIGHT = 3; + const path = generateRandomPath(WIDTH,HEIGHT); + + // path에 대한 길 shape를 생성 + const shapes = [getShapeChar(0b0100, getDirection(path[0], path[1]))]; + for(let i=1; i{ + shapeBoard[y*HEIGHT+x] = shapes[i]; + } ); + + const answer = generateAnswer( shapeBoard.join('') ); + const board = generatePiece( shapeBoard.map( (c)=>{ + if(c !== ".") return c; + if(Math.random() > 0.5) return "─"; + return "┘"; + } ).join('') ); + board.forEach( piece=>piece.rotate = randInt(0, 4) ); + + return [answer, board]; +} + +export default generateRandomPuzzle; From 45c9e34a0701204224a0fc4f93656ed8d38b9648 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 17:32:56 +0900 Subject: [PATCH 13/14] =?UTF-8?q?[feat]=20=ED=8D=BC=EC=A6=90=EC=9D=B4=20?= =?UTF-8?q?=EB=9E=9C=EB=8D=A4=ED=95=9C=20=EC=A0=95=EB=8B=B5=EC=9D=84=20?= =?UTF-8?q?=EA=B0=96=EB=8F=84=EB=A1=9D=20=EC=B4=88=EA=B8=B0=ED=99=94?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interactions/v2l/Puzzle.jsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx index 7c7130eb..bb33dfcb 100644 --- a/src/interactions/v2l/Puzzle.jsx +++ b/src/interactions/v2l/Puzzle.jsx @@ -1,4 +1,5 @@ -import { useState, useImperativeHandle } from "react"; +import { useState, useEffect, useImperativeHandle } from "react"; +import generateRandomPuzzle from "./generateRandom.js"; import { generatePiece, generateAnswer, checkPuzzle } from "./utils.js"; import PuzzlePiece from "./PuzzlePiece.jsx"; import style from "./style.module.css"; @@ -27,11 +28,15 @@ function Puzzle({$ref}) ) ); - useImperativeHandle( $ref, ()=>({ - reset() { - setPiece(generatePiece(`│┘──│││┘│`)); - } - }), [] ); + function reset() + { + const [randAnswer, randPiece] = generateRandomPuzzle(); + setAnswer(randAnswer); + setPiece(randPiece); + } + + useEffect(reset, []); + useImperativeHandle( $ref, ()=>({reset}), [] ); const isCorrect = checkPuzzle(piece, answer); From 73b57bc41b9dc903b83593ade1a247b15512ca88 Mon Sep 17 00:00:00 2001 From: lybell-art Date: Tue, 30 Jul 2024 18:05:20 +0900 Subject: [PATCH 14/14] =?UTF-8?q?[chore]=20=EB=A6=B0=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=94=84=EB=A6=AC=ED=8B=B0=EC=96=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/ClientOnly.jsx | 16 +- src/common/Suspense.jsx | 8 +- .../univasalIsland/style.module.css | 120 +++++----- src/interactions/v2l/Puzzle.jsx | 147 ++++++++----- src/interactions/v2l/PuzzlePiece.jsx | 57 ++--- src/interactions/v2l/constants.js | 2 +- src/interactions/v2l/generateRandom.js | 205 +++++++++--------- src/interactions/v2l/index.jsx | 2 +- src/interactions/v2l/style.module.css | 40 ++-- src/interactions/v2l/utils.js | 125 ++++++----- 10 files changed, 395 insertions(+), 327 deletions(-) diff --git a/src/common/ClientOnly.jsx b/src/common/ClientOnly.jsx index 3f452da6..48f0ae32 100644 --- a/src/common/ClientOnly.jsx +++ b/src/common/ClientOnly.jsx @@ -5,24 +5,28 @@ const mountedStore = { listeners: new Set(), mount() { mountedStore.mounted = true; - mountedStore.listeners.forEach( listener=>listener() ); + mountedStore.listeners.forEach((listener) => listener()); }, subscribe(listener) { mountedStore.listeners.add(listener); - return ()=>mountedStore.listeners.delete(listener); + return () => mountedStore.listeners.delete(listener); }, getSnapshot() { return mountedStore.mounted; - } -} + }, +}; /** * react 클라이언트 only 래퍼 입니다. - * 이 래퍼 컴포넌트는 클라이언트에서만 필요한 동작이 필요할 때, SSR시와 하이드레이션시 반환한 함수가 + * 이 래퍼 컴포넌트는 클라이언트에서만 필요한 동작이 필요할 때, SSR시와 하이드레이션시 반환한 함수가 * 동일한 폴백 엘리먼트를 렌더링하도록 보장합니다. */ export default function ClientOnly({ children, fallback }) { - const mounted = useSyncExternalStore(mountedStore.subscribe, mountedStore.getSnapshot, ()=>false); + const mounted = useSyncExternalStore( + mountedStore.subscribe, + mountedStore.getSnapshot, + () => false, + ); useEffect(() => { mountedStore.mount(); }, []); diff --git a/src/common/Suspense.jsx b/src/common/Suspense.jsx index 59a1f7d0..d2263cea 100644 --- a/src/common/Suspense.jsx +++ b/src/common/Suspense.jsx @@ -9,7 +9,9 @@ import ClientOnly from "./ClientOnly.jsx"; * 출처 : https://toss.tech/article/faster-initial-rendering */ export default function Suspense({ children, fallback }) { - return - {children} - ; + return ( + + {children} + + ); } diff --git a/src/interactions/univasalIsland/style.module.css b/src/interactions/univasalIsland/style.module.css index 97394981..a0299b77 100644 --- a/src/interactions/univasalIsland/style.module.css +++ b/src/interactions/univasalIsland/style.module.css @@ -1,75 +1,75 @@ .hull { - bottom: min(calc(100% - 800px),-140px); + bottom: min(calc(100% - 800px), -140px); } .seat { - width: 317.44px; - height: 501.88px; + width: 317.44px; + height: 501.88px; } .island { - width: 158.2px; - height: 546px; + width: 158.2px; + height: 546px; } .phone { - top: 293px; - left: 541px; - width: 54px; - height: 97px; + top: 293px; + left: 541px; + width: 54px; + height: 97px; } .snap { - top: 40px; - left: 21px; - width: 54px; - height: 97px; + top: 40px; + left: 21px; + width: 54px; + height: 97px; } @media (min-width: 1024px) { - .hull { - bottom: min(calc(100% - 900px),-170px); - } - .seat { - width: 385.46px; - height: 610.64px; - } - .island { - width: 192.1px; - height: 663px; - } - .phone { - top: 185px; - left: 528px; - width: 66px; - height: 118px; - } - .snap { - top: 49px; - left: 25px; - width: 66px; - height: 118px; - } + .hull { + bottom: min(calc(100% - 900px), -170px); + } + .seat { + width: 385.46px; + height: 610.64px; + } + .island { + width: 192.1px; + height: 663px; + } + .phone { + top: 185px; + left: 528px; + width: 66px; + height: 118px; + } + .snap { + top: 49px; + left: 25px; + width: 66px; + height: 118px; + } } @media (min-width: 1280px) { - .hull { - bottom: min(calc(100% - 1000px),-200px); - } - .seat { - width: 453.48px; - height: 718.4px; - } - .island { - width: 226px; - height: 780px; - } - .phone { - top: 75px; - left: 516px; - width: 77px; - height: 140px; - } - .snap { - top: 56px; - left: 30px; - width: 77px; - height: 140px; - } -} \ No newline at end of file + .hull { + bottom: min(calc(100% - 1000px), -200px); + } + .seat { + width: 453.48px; + height: 718.4px; + } + .island { + width: 226px; + height: 780px; + } + .phone { + top: 75px; + left: 516px; + width: 77px; + height: 140px; + } + .snap { + top: 56px; + left: 30px; + width: 77px; + height: 140px; + } +} diff --git a/src/interactions/v2l/Puzzle.jsx b/src/interactions/v2l/Puzzle.jsx index bb33dfcb..83baac9d 100644 --- a/src/interactions/v2l/Puzzle.jsx +++ b/src/interactions/v2l/Puzzle.jsx @@ -11,73 +11,102 @@ import pan from "./assets/pan.svg"; // ─│┌┐┘└ -function Puzzle({$ref}) -{ - const [ answer, setAnswer ] = useState( - generateAnswer(` +function Puzzle({ interactCallback, $ref }) { + const [answer, setAnswer] = useState( + generateAnswer(` ─┐. .│. - .└─` - ) - ); - const [ piece, setPiece ] = useState( - generatePiece(` + .└─`), + ); + const [piece, setPiece] = useState( + generatePiece(` │┘─ ─││ - │┘│` - ) - ); + │┘│`), + ); - function reset() - { - const [randAnswer, randPiece] = generateRandomPuzzle(); - setAnswer(randAnswer); - setPiece(randPiece); - } + function reset() { + const [randAnswer, randPiece] = generateRandomPuzzle(); + setAnswer(randAnswer); + setPiece(randPiece); + } - useEffect(reset, []); - useImperativeHandle( $ref, ()=>({reset}), [] ); + useEffect(reset, []); + useImperativeHandle($ref, () => ({ reset }), []); - const isCorrect = checkPuzzle(piece, answer); + const isCorrect = checkPuzzle(piece, answer); - return
-
- start position -
- - - -
-
- { piece.map( (shape, i)=>{ - const onClick = ()=>{ - setPiece( board=>{ - const newBoard = [...board]; - newBoard[i] = board[i].rotated(); - return newBoard; - } ); - } - const fixRotate = ()=>{ - setPiece( board=>{ - const newBoard = [...board]; - newBoard[i] = board[i].fixedRotated(); - return newBoard; - } ); - } + return ( +
+
+ start position +
+ + + +
+
+ {piece.map((shape, i) => { + const onClick = () => { + setPiece((board) => { + const newBoard = [...board]; + newBoard[i] = board[i].rotated(); + return newBoard; + }); + interactCallback?.(); + }; + const fixRotate = () => { + setPiece((board) => { + const newBoard = [...board]; + newBoard[i] = board[i].fixedRotated(); + return newBoard; + }); + }; - return ; - } ) } -
-
-
- - - - start position - -
-
-
+ return ( + + ); + })} +
+
+
+ + + + start position + +
+
+
+ ); } -export default Puzzle; \ No newline at end of file +export default Puzzle; diff --git a/src/interactions/v2l/PuzzlePiece.jsx b/src/interactions/v2l/PuzzlePiece.jsx index a2651274..d39656fb 100644 --- a/src/interactions/v2l/PuzzlePiece.jsx +++ b/src/interactions/v2l/PuzzlePiece.jsx @@ -1,31 +1,38 @@ import { useState } from "react"; import { LINEAR } from "./constants.js"; -function PuzzlePiece({shape, onClick, fixRotate}) -{ - const [fixing, setFixing] = useState(false); - const style = { - transform: `rotate( ${shape.rotate*90}deg)` - }; +function PuzzlePiece({ shape, onClick, fixRotate }) { + const [fixing, setFixing] = useState(false); + const style = { + transform: `rotate( ${shape.rotate * 90}deg)`, + }; - return
{ - onClick(); - setFixing(false); - }} - onTransitionEnd={()=>{ - if(shape.rotate < 4) return; - setFixing(true); - fixRotate(); - }} - > - - - -
+ return ( +
{ + onClick(); + setFixing(false); + }} + onTransitionEnd={() => { + if (shape.rotate < 4) return; + setFixing(true); + fixRotate(); + }} + > + + + +
+ ); } -export default PuzzlePiece; \ No newline at end of file +export default PuzzlePiece; diff --git a/src/interactions/v2l/constants.js b/src/interactions/v2l/constants.js index 05041ee9..cd3e19d6 100644 --- a/src/interactions/v2l/constants.js +++ b/src/interactions/v2l/constants.js @@ -1,3 +1,3 @@ export const LINEAR = Symbol("linear"); export const CURVED = Symbol("curved"); -export const ANY = Symbol("any"); \ No newline at end of file +export const ANY = Symbol("any"); diff --git a/src/interactions/v2l/generateRandom.js b/src/interactions/v2l/generateRandom.js index 621733f9..ee81fa60 100644 --- a/src/interactions/v2l/generateRandom.js +++ b/src/interactions/v2l/generateRandom.js @@ -1,113 +1,124 @@ import { generatePiece, generateAnswer } from "./utils.js"; -const dir = [ [1,0], [0,1], [-1,0], [0,-1] ]; +const dir = [ + [1, 0], + [0, 1], + [-1, 0], + [0, -1], +]; -function randInt(min, max) -{ - return Math.floor( Math.random() * (max-min) ) + min; +function randInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; } -function generateRandomPath(width, height) -{ - let traced = Array.from({length: width}, ()=>new Array(height).fill(false)); - const stack = []; - let cursor = [0,0]; - - function getNextCursorHubo([x,y]) - { - return dir - .map( ([dx, dy])=>[x+dx, y+dy] ) - .filter( ([tx, ty])=>{ - if(tx < 0 || ty < 0) return false; - if(tx >= width || ty >= height) return false; - if(traced[tx][ty] ) return false; - return true; - } ); - } - - while(cursor[0] !== width-1 || cursor[1] !== height-1) - { - traced[cursor[0]][cursor[1]] = true; - let hubo = getNextCursorHubo(cursor); - // backtracking - if(hubo.length === 0) - { - while(stack.length > 0) - { - let backtrackedCursor = stack[stack.length-1]; - hubo = getNextCursorHubo(backtrackedCursor); - if(hubo.length !== 0) break; - stack.pop(); - } - } - else stack.push(cursor); - cursor = hubo[randInt(0,hubo.length)]; - } - stack.push(cursor); - - return stack; +function generateRandomPath(width, height) { + let traced = Array.from({ length: width }, () => + new Array(height).fill(false), + ); + const stack = []; + let cursor = [0, 0]; + + function getNextCursorHubo([x, y]) { + return dir + .map(([dx, dy]) => [x + dx, y + dy]) + .filter(([tx, ty]) => { + if (tx < 0 || ty < 0) return false; + if (tx >= width || ty >= height) return false; + if (traced[tx][ty]) return false; + return true; + }); + } + + while (cursor[0] !== width - 1 || cursor[1] !== height - 1) { + traced[cursor[0]][cursor[1]] = true; + let hubo = getNextCursorHubo(cursor); + // backtracking + if (hubo.length === 0) { + while (stack.length > 0) { + let backtrackedCursor = stack[stack.length - 1]; + hubo = getNextCursorHubo(backtrackedCursor); + if (hubo.length !== 0) break; + stack.pop(); + } + } else stack.push(cursor); + cursor = hubo[randInt(0, hubo.length)]; + } + stack.push(cursor); + + return stack; } -function getDirection(base, target) -{ - let dx = target[0] - base[0]; - let dy = target[1] - base[1]; +function getDirection(base, target) { + let dx = target[0] - base[0]; + let dy = target[1] - base[1]; - if(dx === 0 && dy === 1) return 0b0001; // down - if(dx === 1 && dy === 0) return 0b0010; // right - if(dx === -1 && dy === 0) return 0b0100; // left - if(dx === 0 && dy === -1) return 0b1000; // up - return 0b0000; + if (dx === 0 && dy === 1) return 0b0001; // down + if (dx === 1 && dy === 0) return 0b0010; // right + if (dx === -1 && dy === 0) return 0b0100; // left + if (dx === 0 && dy === -1) return 0b1000; // up + return 0b0000; } // ─│┌┐┘└ -function getShapeChar(before, after) -{ - const code = before | after; - switch(code) - { - case 0b1100: return "┘"; - case 0b1010: return "└"; - case 0b1001: return "│"; - case 0b0110: return "─"; - case 0b0101: return "┐"; - case 0b0011: return "┌"; - default: return "."; - } +function getShapeChar(before, after) { + const code = before | after; + switch (code) { + case 0b1100: + return "┘"; + case 0b1010: + return "└"; + case 0b1001: + return "│"; + case 0b0110: + return "─"; + case 0b0101: + return "┐"; + case 0b0011: + return "┌"; + default: + return "."; + } } -function generateRandomPuzzle() -{ - const WIDTH = 3; - const HEIGHT = 3; - const path = generateRandomPath(WIDTH,HEIGHT); - - // path에 대한 길 shape를 생성 - const shapes = [getShapeChar(0b0100, getDirection(path[0], path[1]))]; - for(let i=1; i{ - shapeBoard[y*HEIGHT+x] = shapes[i]; - } ); - - const answer = generateAnswer( shapeBoard.join('') ); - const board = generatePiece( shapeBoard.map( (c)=>{ - if(c !== ".") return c; - if(Math.random() > 0.5) return "─"; - return "┘"; - } ).join('') ); - board.forEach( piece=>piece.rotate = randInt(0, 4) ); - - return [answer, board]; +function generateRandomPuzzle() { + const WIDTH = 3; + const HEIGHT = 3; + const path = generateRandomPath(WIDTH, HEIGHT); + + // path에 대한 길 shape를 생성 + const shapes = [getShapeChar(0b0100, getDirection(path[0], path[1]))]; + for (let i = 1; i < path.length - 1; i++) { + const before = getDirection(path[i], path[i - 1]); + const after = getDirection(path[i], path[i + 1]); + shapes.push(getShapeChar(before, after)); + } + shapes.push( + getShapeChar( + getDirection(path[path.length - 1], path[path.length - 2]), + 0b0010, + ), + ); + + // shape 리스트를 3x3 그리드에 맞도록 재배열 + + const shapeBoard = new Array(WIDTH * HEIGHT).fill("."); + path.forEach(([x, y], i) => { + shapeBoard[y * HEIGHT + x] = shapes[i]; + }); + + const answer = generateAnswer(shapeBoard.join("")); + const board = generatePiece( + shapeBoard + .map((c) => { + if (c !== ".") return c; + if (Math.random() > 0.5) return "─"; + return "┘"; + }) + .join(""), + ); + board.forEach((piece) => (piece.rotate = randInt(0, 4))); + + return [answer, board]; } export default generateRandomPuzzle; diff --git a/src/interactions/v2l/index.jsx b/src/interactions/v2l/index.jsx index 44d6c75b..e7fce19b 100644 --- a/src/interactions/v2l/index.jsx +++ b/src/interactions/v2l/index.jsx @@ -15,7 +15,7 @@ function V2LInteraction({ interactCallback, $ref }) { />
스켈레톤 그릴 예정
}> - +
diff --git a/src/interactions/v2l/style.module.css b/src/interactions/v2l/style.module.css index c97ac8ad..331e5752 100644 --- a/src/interactions/v2l/style.module.css +++ b/src/interactions/v2l/style.module.css @@ -1,32 +1,32 @@ .container { - top: max( var(--top-area, 11rem), calc( 60% - 11.5rem ) ); - transform: scale(0.7); - --top-area: 11rem; + top: max(var(--top-area, 11rem), calc(60% - 11.5rem)); + transform: scale(0.7); + --top-area: 11rem; } .rotate { - animation: spin 1s linear infinite; + animation: spin 1s linear infinite; } @media (min-width: 768px) { - .container { - --top-area: 12rem; - transform: scale(0.9); - } + .container { + --top-area: 12rem; + transform: scale(0.9); + } } @media (min-width: 1024px) { - .container { - --top-area: 15rem; - transform: scale(1); - } + .container { + --top-area: 15rem; + transform: scale(1); + } } -@keyframes spin{ - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} \ No newline at end of file +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/interactions/v2l/utils.js b/src/interactions/v2l/utils.js index e742499a..2f00b664 100644 --- a/src/interactions/v2l/utils.js +++ b/src/interactions/v2l/utils.js @@ -2,65 +2,80 @@ import { LINEAR, CURVED, ANY } from "./constants.js"; // ─│┌┐┘└ -class PieceData -{ - constructor(shapeChar) - { - switch(shapeChar) - { - case "─": this.type=LINEAR; this.rotate=0; break; - case "│": this.type=LINEAR; this.rotate=1; break; - case "┌": this.type=CURVED; this.rotate=0; break; - case "┐": this.type=CURVED; this.rotate=1; break; - case "┘": this.type=CURVED; this.rotate=2; break; - case "└": this.type=CURVED; this.rotate=3; break; - } - this.symbol = shapeChar; - } - rotated() - { - const newPiece = new PieceData(this.symbol); - newPiece.rotate = this.rotate + 1; - return newPiece; - } - isCorrect(answer) - { - if(answer === ANY) return true; - if(this.type === LINEAR) return this.rotate % 2 === answer; - return this.rotate % 4 === answer; - } - fixedRotated() - { - const newPiece = new PieceData(this.symbol); - newPiece.rotate = this.rotate % 4; - return newPiece; - } +class PieceData { + constructor(shapeChar) { + switch (shapeChar) { + case "─": + this.type = LINEAR; + this.rotate = 0; + break; + case "│": + this.type = LINEAR; + this.rotate = 1; + break; + case "┌": + this.type = CURVED; + this.rotate = 0; + break; + case "┐": + this.type = CURVED; + this.rotate = 1; + break; + case "┘": + this.type = CURVED; + this.rotate = 2; + break; + case "└": + this.type = CURVED; + this.rotate = 3; + break; + } + this.symbol = shapeChar; + } + rotated() { + const newPiece = new PieceData(this.symbol); + newPiece.rotate = this.rotate + 1; + return newPiece; + } + isCorrect(answer) { + if (answer === ANY) return true; + if (this.type === LINEAR) return this.rotate % 2 === answer; + return this.rotate % 4 === answer; + } + fixedRotated() { + const newPiece = new PieceData(this.symbol); + newPiece.rotate = this.rotate % 4; + return newPiece; + } } -export function generatePiece(shapeString) -{ - const rawString = [...shapeString.replace(/\s+/gm, "")]; - return rawString.map( c=>new PieceData(c) ); +export function generatePiece(shapeString) { + const rawString = [...shapeString.replace(/\s+/gm, "")]; + return rawString.map((c) => new PieceData(c)); } -export function generateAnswer(shapeString) -{ - const rawString = [...shapeString.replace(/\s+/gm, "")]; - return rawString.map( c=>{ - switch(c) - { - case "─": return 0; - case "│": return 1; - case "┌": return 0; - case "┐": return 1; - case "┘": return 2; - case "└": return 3; - default: return ANY; - } - } ); +export function generateAnswer(shapeString) { + const rawString = [...shapeString.replace(/\s+/gm, "")]; + return rawString.map((c) => { + switch (c) { + case "─": + return 0; + case "│": + return 1; + case "┌": + return 0; + case "┐": + return 1; + case "┘": + return 2; + case "└": + return 3; + default: + return ANY; + } + }); } -export function checkPuzzle(pieces, answer) -{ - return pieces.every( (piece, i)=>piece.isCorrect(answer[i]) ); +export function checkPuzzle(pieces, answer) { + return pieces.every((piece, i) => piece.isCorrect(answer[i])); }