Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Sign in / Register
Toggle navigation
T
test
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Смирнов Олег
test
Commits
ebe4288e
Commit
ebe4288e
authored
Dec 16, 2024
by
Smirnov Oleg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
pretty
parent
be00b7bf
Pipeline
#167
failed with stages
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
107 additions
and
52 deletions
+107
-52
page.tsx
src/app/page.tsx
+32
-35
GameBoard.tsx
src/components/GameBoard.tsx
+72
-14
PlayerInfo.tsx
src/components/PlayerInfo.tsx
+3
-3
No files found.
src/app/page.tsx
View file @
ebe4288e
...
@@ -141,7 +141,7 @@ export default function Home() {
...
@@ -141,7 +141,7 @@ export default function Home() {
)
}
)
}
{
currentPlayer
&&
(
{
currentPlayer
&&
(
<
div
className=
"fixed top-4 left-4 bg-gray-800 rounded p-2"
>
<
div
className=
"fixed top-4 left-4 bg-gray-800 rounded p-2
z-50
"
>
<
PlayerInfo
<
PlayerInfo
nickname=
{
`${currentPlayer.nickname} (${currentPlayer.score})`
}
nickname=
{
`${currentPlayer.nickname} (${currentPlayer.score})`
}
isCurrentTurn=
{
gameState
.
isYourTurn
}
isCurrentTurn=
{
gameState
.
isYourTurn
}
...
@@ -151,7 +151,7 @@ export default function Home() {
...
@@ -151,7 +151,7 @@ export default function Home() {
</
div
>
</
div
>
)
}
)
}
{
opponent
&&
(
{
opponent
&&
(
<
div
className=
"fixed bottom-4 left-4 bg-gray-800 rounded p-2"
>
<
div
className=
"fixed bottom-4 left-4 bg-gray-800 rounded p-2
z-50
"
>
<
PlayerInfo
<
PlayerInfo
nickname=
{
`${opponent.nickname} (${opponent.score})`
}
nickname=
{
`${opponent.nickname} (${opponent.score})`
}
isCurrentTurn=
{
!
gameState
.
isYourTurn
}
isCurrentTurn=
{
!
gameState
.
isYourTurn
}
...
@@ -161,45 +161,42 @@ export default function Home() {
...
@@ -161,45 +161,42 @@ export default function Home() {
</
div
>
</
div
>
)
}
)
}
<
div
className=
"
pt-20 pb-20
"
>
<
div
className=
"
h-screen
"
>
{
gameState
.
status
===
'waiting'
?
(
{
gameState
.
status
===
'waiting'
?
(
<
div
className=
"text-center text-xl"
>
<
div
className=
"text-center text-xl"
>
Waiting for opponent to join...
Waiting for opponent to join...
</
div
>
</
div
>
)
:
(
)
:
(
<>
<
GameBoard
<
div
className=
"max-w-screen-sm mx-auto"
>
cells=
{
gameState
.
cells
}
<
GameBoard
onCellClick=
{
handleMove
}
cells=
{
gameState
.
cells
}
isYourTurn=
{
gameState
.
isYourTurn
}
onCellClick=
{
handleMove
}
playerSymbol=
{
gameState
.
playerSymbol
}
isYourTurn=
{
gameState
.
isYourTurn
}
lastMove=
{
gameState
.
lastMove
}
playerSymbol=
{
gameState
.
playerSymbol
}
turnTimeLeft=
{
gameState
.
isYourTurn
?
turnTimeLeft
:
undefined
}
lastMove=
{
gameState
.
lastMove
}
/>
turnTimeLeft=
{
gameState
.
isYourTurn
?
turnTimeLeft
:
undefined
}
)
}
/>
</
div
>
{
gameState
.
winner
&&
(
{
gameState
.
winner
&&
(
<
div
className=
"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<
div
className=
"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<
div
className=
"bg-gray-800 p-8 rounded-lg text-center"
>
<
div
className=
"bg-gray-800 p-8 rounded-lg text-center"
>
<
h2
className=
"text-3xl font-bold mb-4"
>
<
h2
className=
"text-3xl font-bold mb-4"
>
{
(
gameState
.
winner
===
'X'
&&
currentPlayer
?.
isAttacker
)
||
{
(
gameState
.
winner
===
'X'
&&
currentPlayer
?.
isAttacker
)
||
(
gameState
.
winner
===
'O'
&&
!
currentPlayer
?.
isAttacker
)
(
gameState
.
winner
===
'O'
&&
!
currentPlayer
?.
isAttacker
)
?
'Вы победили!'
?
'Вы победили!'
:
'Вы проиграли!'
}
:
'Вы проиграли!'
}
</
h2
>
</
h2
>
<
div
className=
"text-xl mb-4"
>
<
div
className=
"text-xl mb-4"
>
Счет:
{
currentPlayer
?.
nickname
}
(
{
currentPlayer
?.
score
}
) -
{
opponent
?.
nickname
}
(
{
opponent
?.
score
}
)
Счет:
{
currentPlayer
?.
nickname
}
(
{
currentPlayer
?.
score
}
) -
{
opponent
?.
nickname
}
(
{
opponent
?.
score
}
)
</
div
>
<
button
onClick=
{
handleNewGame
}
className=
"px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
Играть снова
</
button
>
</
div
>
</
div
>
</
div
>
)
}
<
button
</>
onClick=
{
handleNewGame
}
className=
"px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
>
Играть снова
</
button
>
</
div
>
</
div
>
)
}
)
}
</
div
>
</
div
>
</
div
>
</
div
>
...
...
src/components/GameBoard.tsx
View file @
ebe4288e
...
@@ -22,6 +22,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -22,6 +22,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
const
[
viewportPosition
,
setViewportPosition
]
=
useState
({
x
:
0
,
y
:
0
});
const
[
viewportPosition
,
setViewportPosition
]
=
useState
({
x
:
0
,
y
:
0
});
const
[
isDragging
,
setIsDragging
]
=
useState
(
false
);
const
[
isDragging
,
setIsDragging
]
=
useState
(
false
);
const
[
dragStart
,
setDragStart
]
=
useState
({
x
:
0
,
y
:
0
});
const
[
dragStart
,
setDragStart
]
=
useState
({
x
:
0
,
y
:
0
});
const
[
isAnimatingMove
,
setIsAnimatingMove
]
=
useState
(
false
);
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
...
@@ -30,20 +31,60 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -30,20 +31,60 @@ const GameBoard: React.FC<GameBoardProps> = ({
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
containerRef
.
current
)
{
if
(
containerRef
.
current
)
{
const
container
=
containerRef
.
current
;
setViewportPosition
({
setViewportPosition
({
x
:
viewportSize
/
2
,
x
:
container
.
clientWidth
/
2
,
y
:
viewportSize
/
2
y
:
container
.
clientHeight
/
2
});
});
}
}
},
[]);
},
[]);
useEffect
(()
=>
{
if
(
lastMove
&&
isAnimatingMove
)
{
const
container
=
containerRef
.
current
;
if
(
!
container
)
return
;
const
targetX
=
container
.
clientWidth
/
2
-
lastMove
.
x
*
cellSize
;
const
targetY
=
container
.
clientHeight
/
2
-
lastMove
.
y
*
cellSize
;
// Анимируем перемещение к последнему ходу
const
startX
=
viewportPosition
.
x
;
const
startY
=
viewportPosition
.
y
;
const
startTime
=
performance
.
now
();
const
duration
=
1000
;
// 1 секунда на анимацию
const
animate
=
(
currentTime
:
number
)
=>
{
const
elapsed
=
currentTime
-
startTime
;
const
progress
=
Math
.
min
(
elapsed
/
duration
,
1
);
// Функция плавности (ease-in-out)
const
easeProgress
=
progress
<
0.5
?
4
*
progress
*
progress
*
progress
:
1
-
Math
.
pow
(
-
2
*
progress
+
2
,
3
)
/
2
;
setViewportPosition
({
x
:
startX
+
(
targetX
-
startX
)
*
easeProgress
,
y
:
startY
+
(
targetY
-
startY
)
*
easeProgress
});
if
(
progress
<
1
)
{
requestAnimationFrame
(
animate
);
}
else
{
setIsAnimatingMove
(
false
);
}
};
requestAnimationFrame
(
animate
);
}
},
[
lastMove
,
isAnimatingMove
,
cellSize
]);
const
getCellValue
=
(
x
:
number
,
y
:
number
):
CellValue
=>
{
const
getCellValue
=
(
x
:
number
,
y
:
number
):
CellValue
=>
{
const
key
=
`
${
x
}
,
${
y
}
`
;
const
key
=
`
${
x
}
,
${
y
}
`
;
return
cells
[
key
]
||
null
;
return
cells
[
key
]
||
null
;
};
};
const
handleCellClick
=
(
x
:
number
,
y
:
number
)
=>
{
const
handleCellClick
=
(
x
:
number
,
y
:
number
)
=>
{
if
(
!
isDragging
)
{
if
(
!
isDragging
&&
!
isAnimatingMove
)
{
const
value
=
getCellValue
(
x
,
y
);
const
value
=
getCellValue
(
x
,
y
);
if
(
!
value
&&
isYourTurn
)
{
if
(
!
value
&&
isYourTurn
)
{
onCellClick
(
x
,
y
);
onCellClick
(
x
,
y
);
...
@@ -52,6 +93,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -52,6 +93,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
};
};
const
handleMouseDown
=
(
e
:
React
.
MouseEvent
)
=>
{
const
handleMouseDown
=
(
e
:
React
.
MouseEvent
)
=>
{
if
(
isAnimatingMove
)
return
;
setIsDragging
(
true
);
setIsDragging
(
true
);
setDragStart
({
setDragStart
({
x
:
e
.
clientX
-
viewportPosition
.
x
,
x
:
e
.
clientX
-
viewportPosition
.
x
,
...
@@ -60,7 +102,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -60,7 +102,7 @@ const GameBoard: React.FC<GameBoardProps> = ({
};
};
const
handleMouseMove
=
(
e
:
React
.
MouseEvent
)
=>
{
const
handleMouseMove
=
(
e
:
React
.
MouseEvent
)
=>
{
if
(
isDragging
)
{
if
(
isDragging
&&
!
isAnimatingMove
)
{
setViewportPosition
({
setViewportPosition
({
x
:
e
.
clientX
-
dragStart
.
x
,
x
:
e
.
clientX
-
dragStart
.
x
,
y
:
e
.
clientY
-
dragStart
.
y
y
:
e
.
clientY
-
dragStart
.
y
...
@@ -102,13 +144,15 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -102,13 +144,15 @@ const GameBoard: React.FC<GameBoardProps> = ({
const
value
=
getCellValue
(
x
,
y
);
const
value
=
getCellValue
(
x
,
y
);
const
key
=
`
${
x
}
,
${
y
}
`
;
const
key
=
`
${
x
}
,
${
y
}
`
;
const
isHoverable
=
!
value
&&
isYourTurn
;
const
isHoverable
=
!
value
&&
isYourTurn
;
const
isLastMove
=
lastMove
&&
lastMove
.
x
===
x
&&
lastMove
.
y
===
y
;
return
(
return
(
<
div
<
div
key=
{
key
}
key=
{
key
}
onClick=
{
()
=>
handleCellClick
(
x
,
y
)
}
onClick=
{
()
=>
handleCellClick
(
x
,
y
)
}
className=
{
`absolute flex items-center justify-center w-[58px] h-[58px] border border-gray-700 bg-gray-800
className=
{
`absolute flex items-center justify-center w-[58px] h-[58px] border border-gray-700 bg-gray-800
transition-all duration-200 ${isHoverable ? 'hover:bg-gray-700 cursor-pointer' : ''}`
}
transition-all duration-200 ${isHoverable ? 'hover:bg-gray-700 cursor-pointer' : ''}
${isLastMove ? 'ring-2 ring-yellow-500' : ''}`
}
style=
{
{
style=
{
{
left
:
x
*
cellSize
,
left
:
x
*
cellSize
,
top
:
y
*
cellSize
,
top
:
y
*
cellSize
,
...
@@ -116,7 +160,8 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -116,7 +160,8 @@ const GameBoard: React.FC<GameBoardProps> = ({
}
}
}
}
>
>
{
value
&&
(
{
value
&&
(
<
div
className=
"transform transition-transform duration-200 scale-100"
>
<
div
className=
{
`transform transition-transform duration-200 scale-100
${isLastMove ? 'animate-pulse' : ''}`
}
>
<
ServerIcon
state=
{
getServerIconState
(
value
)
}
/>
<
ServerIcon
state=
{
getServerIconState
(
value
)
}
/>
</
div
>
</
div
>
)
}
)
}
...
@@ -130,11 +175,8 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -130,11 +175,8 @@ const GameBoard: React.FC<GameBoardProps> = ({
};
};
const
moveToLastMove
=
()
=>
{
const
moveToLastMove
=
()
=>
{
if
(
lastMove
)
{
if
(
lastMove
&&
!
isAnimatingMove
)
{
setViewportPosition
({
setIsAnimatingMove
(
true
);
x
:
viewportSize
/
2
-
lastMove
.
x
*
cellSize
,
y
:
viewportSize
/
2
-
lastMove
.
y
*
cellSize
});
}
}
};
};
...
@@ -142,12 +184,27 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -142,12 +184,27 @@ const GameBoard: React.FC<GameBoardProps> = ({
<
div
className=
"relative"
>
<
div
className=
"relative"
>
<
div
<
div
ref=
{
containerRef
}
ref=
{
containerRef
}
className=
{
`relative w-
[800px] h-[800px]
bg-gray-900 overflow-hidden mx-auto
className=
{
`relative w-
screen h-screen
bg-gray-900 overflow-hidden mx-auto
${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`
}
${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`
}
onMouseDown=
{
handleMouseDown
}
onMouseDown=
{
handleMouseDown
}
onMouseMove=
{
handleMouseMove
}
onMouseMove=
{
handleMouseMove
}
onMouseUp=
{
handleMouseUp
}
onMouseUp=
{
handleMouseUp
}
onMouseLeave=
{
handleMouseUp
}
onMouseLeave=
{
handleMouseUp
}
onTouchStart=
{
(
e
)
=>
{
const
touch
=
e
.
touches
[
0
];
handleMouseDown
({
clientX
:
touch
.
clientX
,
clientY
:
touch
.
clientY
}
as
React
.
MouseEvent
);
}
}
onTouchMove=
{
(
e
)
=>
{
const
touch
=
e
.
touches
[
0
];
handleMouseMove
({
clientX
:
touch
.
clientX
,
clientY
:
touch
.
clientY
}
as
React
.
MouseEvent
);
}
}
onTouchEnd=
{
handleMouseUp
}
>
>
<
div
<
div
style=
{
{
style=
{
{
...
@@ -159,13 +216,14 @@ const GameBoard: React.FC<GameBoardProps> = ({
...
@@ -159,13 +216,14 @@ const GameBoard: React.FC<GameBoardProps> = ({
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"
absolute top-4 right-4 flex flex-col gap-2
"
>
<
div
className=
"
fixed top-4 right-4 flex flex-col gap-2 z-50
"
>
{
lastMove
&&
(
{
lastMove
&&
(
<
button
<
button
onClick=
{
moveToLastMove
}
onClick=
{
moveToLastMove
}
className=
"px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded transition-colors"
className=
"px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded transition-colors"
disabled=
{
isAnimatingMove
}
>
>
По
казать по
следний ход
Последний ход
</
button
>
</
button
>
)
}
)
}
{
turnTimeLeft
!==
undefined
&&
(
{
turnTimeLeft
!==
undefined
&&
(
...
...
src/components/PlayerInfo.tsx
View file @
ebe4288e
...
@@ -9,8 +9,8 @@ interface PlayerInfoProps {
...
@@ -9,8 +9,8 @@ interface PlayerInfoProps {
const
PlayerInfo
:
React
.
FC
<
PlayerInfoProps
>
=
({
nickname
,
isCurrentTurn
,
isAttacker
,
position
})
=>
{
const
PlayerInfo
:
React
.
FC
<
PlayerInfoProps
>
=
({
nickname
,
isCurrentTurn
,
isAttacker
,
position
})
=>
{
return
(
return
(
<
div
className=
{
`fixed ${position === 'top' ? 'top-
4' : 'bottom-4'} left-4 flex
items-center gap-4 bg-gray-800 rounded-lg p-4 text-white`
}
>
<
div
className=
{
`fixed ${position === 'top' ? 'top-
0' : 'bottom-0'} left-0 flex
items-center gap-4 bg-gray-800 rounded-lg p-4 text-white`
}
>
<
div
className=
{
`w-12 h-12 rounded-full flex items-center justify-center ${
<
div
className=
{
`w-12 h-12 rounded-full flex items-center justify-center
${
isCurrentTurn ? (isAttacker ? 'bg-red-500' : 'bg-green-500') : 'bg-gray-600'
isCurrentTurn ? (isAttacker ? 'bg-red-500' : 'bg-green-500') : 'bg-gray-600'
}`
}
>
}`
}
>
{
nickname
.
charAt
(
0
).
toUpperCase
()
}
{
nickname
.
charAt
(
0
).
toUpperCase
()
}
...
@@ -18,7 +18,7 @@ const PlayerInfo: React.FC<PlayerInfoProps> = ({ nickname, isCurrentTurn, isAtta
...
@@ -18,7 +18,7 @@ const PlayerInfo: React.FC<PlayerInfoProps> = ({ nickname, isCurrentTurn, isAtta
<
div
>
<
div
>
<
div
className=
"font-medium"
>
{
nickname
}
</
div
>
<
div
className=
"font-medium"
>
{
nickname
}
</
div
>
<
div
className=
{
`text-sm ${isCurrentTurn ? 'text-yellow-400' : 'text-gray-400'}`
}
>
<
div
className=
{
`text-sm ${isCurrentTurn ? 'text-yellow-400' : 'text-gray-400'}`
}
>
{
isCurrentTurn
?
'
Your turn'
:
'Waiting
...'
}
{
isCurrentTurn
?
'
Твой ход'
:
'Ожидание
...'
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment