Toggle navigation
Toggle navigation
This project
Loading...
Sign in
강동현
/
nodejs-game
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
강동현
2021-06-08 02:20:41 +0900
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
2dce3bd9a1c333311ccff181440bfb11d6c5df28
2dce3bd9
1 parent
01b42c69
Game 파일 이동 및 버그 수정
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
360 additions
and
316 deletions
common/message.ts
server/connection/Connection.ts
server/game/Game.ts
server/game/WordGuessingGame.ts
server/package.json
server/room/Room.ts
common/message.ts
View file @
2dce3bd
...
...
@@ -8,6 +8,7 @@ import {
Record
,
Union
,
Static
,
Optional
,
}
from
"runtypes"
;
import
{
Role
,
...
...
@@ -48,7 +49,12 @@ export class ServerInboundMessageRecordMap {
});
// 방장이 게임을 시작합니다.
startGame
=
Record
({});
// TODO: 주의! 아래 필드는 디버그 용도로만 사용됩니다. 추후에 준비 화면에서 공개적으로 설정하는 것으로 구현해야 합니다.
startGame
=
Record
({
maxRound
:
Optional
(
Number
),
roundDuration
:
Optional
(
Number
),
roundTerm
:
Optional
(
Number
),
});
// drawer가 단어를 선택합니다.
chooseWord
=
Record
({
...
...
server/connection/Connection.ts
View file @
2dce3bd
...
...
@@ -54,6 +54,14 @@ export class Connection {
}
// Game > Room > User 순으로 전달
if
(
this
.
user
?.
room
?.
game
)
{
const
response
=
this
.
user
.
room
.
game
.
handler
.
handle
(
type
,
this
.
user
,
message
);
if
(
response
)
return
response
;
}
if
(
this
.
user
?.
room
)
{
const
response
=
this
.
user
.
room
.
handler
.
handle
(
type
,
this
.
user
,
message
);
if
(
response
)
return
response
;
...
...
server/game/Game.ts
View file @
2dce3bd
import
{
Role
}
from
"../../common/dataType"
;
import
{
MessageHandler
}
from
"../message/MessageHandler"
;
import
{
Room
}
from
"../room/Room"
;
import
{
User
}
from
"../user/User"
;
export
interface
Game
{
join
(
user
:
User
):
void
;
leave
(
user
:
User
):
void
;
export
class
Game
{
room
:
Room
;
maxRound
:
number
;
round
:
number
=
0
;
roundState
:
"choosing"
|
"running"
|
"done"
=
"choosing"
;
roundDuration
:
number
;
readonly
roundTerm
:
number
=
5
;
// 다음 라운드 시작까지 기다리는 시간
wordCandidates
:
string
[]
=
[];
word
:
string
=
""
;
timer
:
{
startTimeMillis
:
number
;
timeLeftMillis
:
number
;
running
:
boolean
;
}
=
{
startTimeMillis
:
0
,
timeLeftMillis
:
0
,
running
:
false
};
timeoutTimerId
?:
NodeJS
.
Timeout
;
nextRoundTimerId
?:
NodeJS
.
Timeout
;
brush
:
{
size
:
number
;
color
:
string
;
drawing
:
boolean
;
x
:
number
;
y
:
number
;
}
=
{
size
:
24
,
color
:
"000000"
,
drawing
:
false
,
x
:
0
,
y
:
0
,
};
handler
:
MessageHandler
;
roles
:
Map
<
User
,
Role
>
;
drawer
?:
User
;
constructor
(
room
:
Room
,
maxRound
:
number
,
roundDuration
:
number
,
roundTerm
:
number
)
{
this
.
room
=
room
;
// TODO: 방장이 설정
this
.
maxRound
=
maxRound
;
this
.
roundDuration
=
roundDuration
;
this
.
roundTerm
=
roundTerm
;
this
.
handler
=
new
MessageHandler
({
chooseWord
:
(
user
,
message
)
=>
{
if
(
user
!==
this
.
drawer
||
this
.
roundState
!==
"choosing"
)
{
return
{
ok
:
false
};
}
const
chosen
=
message
.
word
;
if
(
this
.
wordCandidates
.
includes
(
chosen
))
{
this
.
wordSelected
(
chosen
);
return
{
ok
:
true
};
}
return
{
ok
:
false
};
},
chat
:
(
user
,
message
)
=>
{
const
text
=
message
.
message
.
trim
();
if
(
this
.
roles
.
get
(
user
)
===
"guesser"
&&
text
===
this
.
word
)
{
this
.
acceptAnswer
(
user
);
}
else
{
this
.
room
.
sendChat
(
user
,
text
);
}
return
{
ok
:
true
};
},
setBrush
:
(
user
,
message
)
=>
{
if
(
user
!==
this
.
drawer
||
!
/^
[
0-9a-f
]{6}
$/
.
test
(
message
.
color
))
{
return
{
ok
:
false
};
}
this
.
brush
.
size
=
Math
.
max
(
Math
.
min
(
message
.
size
,
64
),
1
);
this
.
brush
.
color
=
message
.
color
;
this
.
brush
.
drawing
=
message
.
drawing
;
this
.
room
.
broadcast
(
"setBrush"
,
{
size
:
this
.
brush
.
size
,
color
:
this
.
brush
.
color
,
drawing
:
this
.
brush
.
drawing
,
},
user
);
return
{
ok
:
true
};
},
moveBrush
:
(
user
,
message
)
=>
{
if
(
user
!==
this
.
drawer
)
{
return
{
ok
:
false
};
}
this
.
brush
.
x
=
Math
.
max
(
Math
.
min
(
message
.
x
,
1
),
0
);
this
.
brush
.
y
=
Math
.
max
(
Math
.
min
(
message
.
y
,
1
),
0
);
this
.
room
.
broadcast
(
"moveBrush"
,
{
x
:
this
.
brush
.
x
,
y
:
this
.
brush
.
y
,
},
user
);
return
{
ok
:
true
};
},
});
this
.
roles
=
new
Map
<
User
,
Role
>
();
this
.
startNextRound
();
}
private
startNextRound
():
void
{
this
.
roundState
=
"choosing"
;
this
.
round
++
;
this
.
roles
.
clear
();
this
.
drawer
=
this
.
pickDrawer
();
this
.
room
.
users
.
forEach
((
user
)
=>
this
.
roles
.
set
(
user
,
"guesser"
));
this
.
roles
.
set
(
this
.
drawer
,
"drawer"
);
this
.
room
.
broadcast
(
"startRound"
,
{
round
:
this
.
round
,
duration
:
this
.
roundDuration
,
roles
:
this
.
makeRoleArray
(),
});
this
.
wordCandidates
=
this
.
pickWords
();
this
.
drawer
.
connection
.
send
(
"wordSet"
,
{
words
:
this
.
wordCandidates
});
}
private
wordSelected
(
word
:
string
):
void
{
this
.
word
=
word
;
this
.
roundState
=
"running"
;
this
.
room
.
broadcast
(
"wordChosen"
,
{
length
:
word
.
length
});
this
.
startTimer
(
this
.
roundDuration
*
1000
);
this
.
timeoutTimerId
=
setTimeout
(
()
=>
this
.
finishRound
(),
this
.
roundDuration
*
1000
);
}
public
finishRound
():
void
{
if
(
this
.
timeoutTimerId
)
{
clearTimeout
(
this
.
timeoutTimerId
);
this
.
timeoutTimerId
=
undefined
;
}
this
.
roundState
=
"done"
;
this
.
stopTimer
();
this
.
room
.
broadcast
(
"finishRound"
,
{
answer
:
this
.
word
});
this
.
prepareNextRound
();
}
private
prepareNextRound
():
void
{
this
.
nextRoundTimerId
=
setTimeout
(()
=>
{
if
(
this
.
round
==
this
.
maxRound
)
{
this
.
finishGame
();
}
else
{
this
.
startNextRound
();
}
},
this
.
roundTerm
*
1000
);
}
private
finishGame
():
void
{
this
.
room
.
broadcast
(
"finishGame"
,
{});
this
.
room
.
finishGame
();
}
private
forceFinishGame
()
{
if
(
this
.
timeoutTimerId
)
{
clearTimeout
(
this
.
timeoutTimerId
);
}
if
(
this
.
nextRoundTimerId
)
{
clearTimeout
(
this
.
nextRoundTimerId
);
}
this
.
room
.
broadcast
(
"finishRound"
,
{
answer
:
this
.
word
});
this
.
finishGame
();
}
private
acceptAnswer
(
user
:
User
):
void
{
user
.
connection
.
send
(
"answerAccepted"
,
{
answer
:
this
.
word
});
this
.
changeRole
(
user
,
"winner"
);
let
noGuesser
=
true
;
this
.
roles
.
forEach
((
role
,
user
)
=>
{
if
(
role
===
"guesser"
)
{
noGuesser
=
false
;
}
});
if
(
noGuesser
)
{
this
.
finishRound
();
}
}
private
pickDrawer
():
User
{
const
candidates
=
this
.
room
.
users
.
filter
((
user
)
=>
user
!==
this
.
drawer
);
return
candidates
[
Math
.
floor
(
Math
.
random
()
*
candidates
.
length
)];
}
private
pickWords
():
string
[]
{
return
[
"장난감"
,
"백화점"
,
"파티"
];
}
private
startTimer
(
timeLeftMillis
:
number
):
void
{
this
.
timer
=
{
startTimeMillis
:
Date
.
now
(),
timeLeftMillis
,
running
:
true
,
};
this
.
room
.
users
.
forEach
((
user
)
=>
this
.
sendTimer
(
user
));
}
private
stopTimer
():
void
{
this
.
timer
=
{
...
this
.
timer
,
running
:
false
,
};
this
.
room
.
users
.
forEach
((
user
)
=>
this
.
sendTimer
(
user
));
}
private
sendTimer
(
user
:
User
):
void
{
user
.
connection
.
send
(
"timer"
,
{
state
:
this
.
timer
.
running
?
"started"
:
"stopped"
,
time
:
Math
.
max
(
(
this
.
timer
.
startTimeMillis
+
this
.
timer
.
timeLeftMillis
-
Date
.
now
())
/
1000
,
0
),
});
}
private
makeRoleArray
():
{
username
:
string
;
role
:
Role
}[]
{
let
roleArray
:
{
username
:
string
;
role
:
Role
;
}[]
=
[];
this
.
roles
.
forEach
((
role
,
user
)
=>
roleArray
.
push
({
username
:
user
.
username
,
role
:
role
})
);
return
roleArray
;
}
private
changeRole
(
user
:
User
,
role
:
Role
)
{
this
.
roles
.
set
(
user
,
role
);
this
.
room
.
broadcast
(
"role"
,
{
username
:
user
.
username
,
role
});
}
join
(
user
:
User
):
void
{
this
.
changeRole
(
user
,
"spectator"
);
this
.
sendTimer
(
user
);
user
.
connection
.
send
(
"startRound"
,
{
round
:
this
.
round
,
duration
:
this
.
roundDuration
,
roles
:
this
.
makeRoleArray
(),
});
if
(
this
.
roundState
===
"done"
)
{
user
.
connection
.
send
(
"finishRound"
,
{
answer
:
this
.
word
,
});
}
user
.
connection
.
send
(
"setBrush"
,
{
size
:
this
.
brush
.
size
,
color
:
this
.
brush
.
color
,
drawing
:
this
.
brush
.
drawing
,
});
user
.
connection
.
send
(
"moveBrush"
,
{
x
:
this
.
brush
.
x
,
y
:
this
.
brush
.
y
,
});
}
leave
(
user
:
User
):
void
{
if
(
this
.
room
.
users
.
length
<
2
)
{
this
.
forceFinishGame
();
return
;
}
this
.
roles
.
delete
(
user
);
if
(
user
===
this
.
drawer
)
{
if
(
this
.
roundState
===
"choosing"
)
{
this
.
round
--
;
// 이번 라운드를 다시 시작
this
.
startNextRound
();
}
else
if
(
this
.
roundState
===
"running"
)
{
this
.
finishRound
();
}
}
else
{
let
guesserCount
=
0
;
this
.
roles
.
forEach
((
role
,
user
)
=>
{
if
(
role
===
"guesser"
)
{
guesserCount
++
;
}
});
if
(
guesserCount
<
1
)
{
if
(
this
.
roundState
===
"choosing"
)
{
this
.
round
--
;
this
.
startNextRound
();
}
else
if
(
this
.
roundState
===
"running"
)
{
this
.
finishRound
();
}
}
}
}
}
...
...
server/game/WordGuessingGame.ts
deleted
100644 → 0
View file @
01b42c6
import
{
Role
}
from
"../../common/dataType"
;
import
{
MessageHandler
}
from
"../message/MessageHandler"
;
import
{
Room
}
from
"../room/Room"
;
import
{
User
}
from
"../user/User"
;
import
{
Game
}
from
"./Game"
;
export
class
WorldGuessingGame
implements
Game
{
room
:
Room
;
maxRound
:
number
;
round
:
number
=
0
;
roundState
:
"choosing"
|
"running"
|
"done"
=
"choosing"
;
roundDuration
:
number
;
readonly
roundTerm
:
number
=
5
;
// 다음 라운드 시작까지 기다리는 시간
wordCandidates
:
string
[]
=
[];
word
:
string
=
""
;
timer
:
{
startTimeMillis
:
number
;
timeLeftMillis
:
number
;
running
:
boolean
;
}
=
{
startTimeMillis
:
0
,
timeLeftMillis
:
0
,
running
:
false
};
timeoutTimerId
?:
NodeJS
.
Timeout
;
nextRoundTimerId
?:
NodeJS
.
Timeout
;
brush
:
{
size
:
number
;
color
:
string
;
drawing
:
boolean
;
x
:
number
;
y
:
number
;
}
=
{
size
:
24
,
color
:
"000000"
,
drawing
:
false
,
x
:
0
,
y
:
0
,
};
handler
:
MessageHandler
;
roles
:
Map
<
User
,
Role
>
;
drawer
?:
User
;
constructor
(
room
:
Room
)
{
this
.
room
=
room
;
// TODO: 방장이 설정
this
.
maxRound
=
5
;
this
.
roundDuration
=
60
;
this
.
handler
=
new
MessageHandler
({
chooseWord
:
(
user
,
message
)
=>
{
if
(
user
!==
this
.
drawer
||
this
.
roundState
===
"choosing"
)
{
return
{
ok
:
false
};
}
const
chosen
=
message
.
word
;
if
(
this
.
wordCandidates
.
includes
(
chosen
))
{
this
.
wordSelected
(
chosen
);
return
{
ok
:
true
};
}
return
{
ok
:
false
};
},
chat
:
(
user
,
message
)
=>
{
const
text
=
message
.
message
.
trim
();
if
(
this
.
roles
.
get
(
user
)
===
"guesser"
&&
text
===
this
.
word
)
{
this
.
acceptAnswer
(
user
);
}
else
{
this
.
room
.
sendChat
(
user
,
text
);
}
return
{
ok
:
true
};
},
setBrush
:
(
user
,
message
)
=>
{
if
(
user
!==
this
.
drawer
||
!
/^
[
0-9a-f
]{6}
$/
.
test
(
message
.
color
))
{
return
{
ok
:
false
};
}
this
.
brush
.
size
=
Math
.
max
(
Math
.
min
(
message
.
size
,
64
),
1
);
this
.
brush
.
color
=
message
.
color
;
this
.
brush
.
drawing
=
message
.
drawing
;
this
.
room
.
broadcast
(
"setBrush"
,
{
size
:
this
.
brush
.
size
,
color
:
this
.
brush
.
color
,
drawing
:
this
.
brush
.
drawing
,
},
user
);
return
{
ok
:
true
};
},
moveBrush
:
(
user
,
message
)
=>
{
if
(
user
!==
this
.
drawer
)
{
return
{
ok
:
false
};
}
this
.
brush
.
x
=
Math
.
max
(
Math
.
min
(
message
.
x
,
1
),
0
);
this
.
brush
.
y
=
Math
.
max
(
Math
.
min
(
message
.
y
,
1
),
0
);
this
.
room
.
broadcast
(
"moveBrush"
,
{
x
:
this
.
brush
.
x
,
y
:
this
.
brush
.
y
,
},
user
);
return
{
ok
:
true
};
},
});
this
.
roles
=
new
Map
<
User
,
Role
>
();
this
.
startNextRound
();
}
private
startNextRound
():
void
{
this
.
roundState
=
"choosing"
;
this
.
round
++
;
this
.
roles
.
clear
();
this
.
drawer
=
this
.
pickDrawer
();
this
.
room
.
users
.
forEach
((
user
)
=>
this
.
roles
.
set
(
user
,
"guesser"
));
this
.
roles
.
set
(
this
.
drawer
,
"drawer"
);
this
.
room
.
broadcast
(
"startRound"
,
{
round
:
this
.
round
,
duration
:
this
.
roundDuration
,
roles
:
this
.
makeRoleArray
(),
});
this
.
wordCandidates
=
this
.
pickWords
();
this
.
drawer
.
connection
.
send
(
"wordSet"
,
{
words
:
this
.
wordCandidates
});
}
private
wordSelected
(
word
:
string
):
void
{
this
.
word
=
word
;
this
.
roundState
=
"running"
;
this
.
room
.
broadcast
(
"wordChosen"
,
{
length
:
word
.
length
});
this
.
startTimer
(
this
.
roundDuration
*
1000
);
this
.
timeoutTimerId
=
setTimeout
(
this
.
finishRound
,
this
.
roundDuration
*
1000
);
}
private
finishRound
():
void
{
if
(
this
.
timeoutTimerId
)
{
clearTimeout
(
this
.
timeoutTimerId
);
this
.
timeoutTimerId
=
undefined
;
}
this
.
roundState
=
"done"
;
this
.
stopTimer
();
this
.
room
.
broadcast
(
"finishRound"
,
{
answer
:
this
.
word
});
this
.
prepareNextRound
();
}
private
prepareNextRound
():
void
{
this
.
nextRoundTimerId
=
setTimeout
(()
=>
{
if
(
this
.
round
==
this
.
maxRound
)
{
this
.
finishGame
();
}
else
{
this
.
startNextRound
();
}
},
this
.
roundTerm
*
1000
);
}
private
finishGame
():
void
{
this
.
room
.
broadcast
(
"finishGame"
,
{});
this
.
room
.
finishGame
();
}
private
forceFinishGame
()
{
if
(
this
.
timeoutTimerId
)
{
clearTimeout
(
this
.
timeoutTimerId
);
}
if
(
this
.
nextRoundTimerId
)
{
clearTimeout
(
this
.
nextRoundTimerId
);
}
this
.
room
.
broadcast
(
"finishRound"
,
{
answer
:
this
.
word
});
this
.
finishGame
();
}
private
acceptAnswer
(
user
:
User
):
void
{
user
.
connection
.
send
(
"answerAccepted"
,
{
answer
:
this
.
word
});
this
.
changeRole
(
user
,
"winner"
);
}
private
pickDrawer
():
User
{
const
candidates
=
this
.
room
.
users
.
filter
((
user
)
=>
user
!==
this
.
drawer
);
return
candidates
[
Math
.
floor
(
Math
.
random
()
*
candidates
.
length
)];
}
private
pickWords
():
string
[]
{
return
[
"장난감"
,
"백화점"
,
"파티"
];
}
private
startTimer
(
timeLeftMillis
:
number
):
void
{
this
.
timer
=
{
startTimeMillis
:
Date
.
now
(),
timeLeftMillis
,
running
:
true
,
};
}
private
stopTimer
():
void
{
this
.
timer
=
{
...
this
.
timer
,
running
:
false
,
};
this
.
room
.
users
.
forEach
((
user
)
=>
this
.
sendTimer
(
user
));
}
private
sendTimer
(
user
:
User
):
void
{
user
.
connection
.
send
(
"timer"
,
{
state
:
this
.
timer
.
running
?
"started"
:
"stopped"
,
time
:
Math
.
max
(
(
this
.
timer
.
startTimeMillis
+
this
.
timer
.
timeLeftMillis
-
Date
.
now
())
/
1000
,
0
),
});
}
private
makeRoleArray
():
{
username
:
string
;
role
:
Role
}[]
{
let
roleArray
:
{
username
:
string
;
role
:
Role
;
}[]
=
[];
this
.
roles
.
forEach
((
role
,
user
)
=>
roleArray
.
push
({
username
:
user
.
username
,
role
:
role
})
);
return
roleArray
;
}
private
changeRole
(
user
:
User
,
role
:
Role
)
{
this
.
roles
.
set
(
user
,
role
);
this
.
room
.
broadcast
(
"role"
,
{
username
:
user
.
username
,
role
});
}
join
(
user
:
User
):
void
{
this
.
changeRole
(
user
,
"spectator"
);
this
.
sendTimer
(
user
);
user
.
connection
.
send
(
"startRound"
,
{
round
:
this
.
round
,
duration
:
this
.
roundDuration
,
roles
:
this
.
makeRoleArray
(),
});
if
(
this
.
roundState
===
"done"
)
{
user
.
connection
.
send
(
"finishRound"
,
{
answer
:
this
.
word
,
});
}
user
.
connection
.
send
(
"setBrush"
,
{
size
:
this
.
brush
.
size
,
color
:
this
.
brush
.
color
,
drawing
:
this
.
brush
.
drawing
,
});
user
.
connection
.
send
(
"moveBrush"
,
{
x
:
this
.
brush
.
x
,
y
:
this
.
brush
.
y
,
});
}
leave
(
user
:
User
):
void
{
if
(
this
.
room
.
users
.
length
<
2
)
{
this
.
forceFinishGame
();
return
;
}
this
.
roles
.
delete
(
user
);
if
(
user
===
this
.
drawer
)
{
if
(
this
.
roundState
===
"choosing"
)
{
this
.
round
--
;
// 이번 라운드를 다시 시작
this
.
startNextRound
();
}
else
if
(
this
.
roundState
===
"running"
)
{
this
.
finishRound
();
}
}
else
{
let
guesserCount
=
0
;
this
.
roles
.
forEach
((
role
,
user
)
=>
{
if
(
role
===
"guesser"
)
{
guesserCount
++
;
}
});
if
(
guesserCount
<
1
)
{
if
(
this
.
roundState
===
"choosing"
)
{
this
.
round
--
;
this
.
startNextRound
();
}
else
if
(
this
.
roundState
===
"running"
)
{
this
.
finishRound
();
}
}
}
}
}
server/package.json
View file @
2dce3bd
...
...
@@ -16,7 +16,7 @@
},
"scripts"
:
{
"start"
:
"nodemon index.ts"
,
"test"
:
"nyc mocha -r ts-node/register ./**/*.test.ts"
,
"test"
:
"nyc mocha -r ts-node/register
--timeout 8000
./**/*.test.ts"
,
"build"
:
"tsc -b -v"
},
"devDependencies"
:
{
...
...
server/room/Room.ts
View file @
2dce3bd
...
...
@@ -10,7 +10,6 @@ import {
import
{
RoomDescription
,
RoomInfo
,
UserData
}
from
"../../common/dataType"
;
import
{
RoomManager
}
from
"./RoomManager"
;
import
{
Game
}
from
"../game/Game"
;
import
{
WorldGuessingGame
}
from
"../game/WordGuessingGame"
;
export
class
Room
{
public
readonly
uuid
:
string
;
...
...
@@ -72,7 +71,23 @@ export class Room {
if
(
!
result
.
ok
)
{
return
result
;
}
this
.
startGame
();
// TODO: 방장이 따로 메세지를 보내 설정할 수 있도록 수정해주세요.
const
settings
=
message
;
if
(
!
settings
.
maxRound
)
{
settings
.
maxRound
=
5
;
}
if
(
!
settings
.
roundDuration
)
{
settings
.
roundDuration
=
60
;
}
if
(
!
settings
.
roundTerm
)
{
settings
.
roundTerm
=
5
;
}
this
.
startGame
(
settings
.
maxRound
,
settings
.
roundDuration
,
settings
.
roundTerm
);
return
{
ok
:
true
};
},
});
...
...
@@ -179,8 +194,12 @@ export class Room {
return
{
ok
:
true
};
}
private
startGame
():
void
{
this
.
game
=
new
WorldGuessingGame
(
this
);
private
startGame
(
maxRound
:
number
,
roundDuration
:
number
,
roundTerm
:
number
):
void
{
this
.
game
=
new
Game
(
this
,
maxRound
,
roundDuration
,
roundTerm
);
}
public
finishGame
():
void
{
...
...
Please
register
or
login
to post a comment