Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
Menteur
Overview
Overview
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
PLN
Menteur
Commits
24645257
Unverified
Commit
24645257
authored
Apr 19, 2020
by
PLN (Algolia)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: Async, announces waiting
parent
357a4209
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
101 additions
and
38 deletions
+101
-38
app.py
server/app.py
+8
-1
lobby.py
server/game/lobby.py
+56
-11
message.py
server/game/message.py
+2
-1
card.py
server/model/card.py
+3
-0
game.py
server/model/game.py
+10
-9
players.py
server/model/players.py
+4
-5
test_data.py
server/test/test_data.py
+2
-2
test_game.py
server/test/test_game.py
+4
-4
test_lobby.py
server/test/test_lobby.py
+5
-5
ws.py
server/ws.py
+7
-0
No files found.
server/app.py
View file @
24645257
...
...
@@ -5,7 +5,7 @@ from starlette.exceptions import HTTPException
from
starlette.responses
import
PlainTextResponse
# Game state
from
server.ws
import
sio
from
server.ws
import
sio
,
lobby
# Server
app
=
FastAPI
()
...
...
@@ -33,6 +33,13 @@ async def hello_world():
return
"Hello, gentle[wo]man"
@router.get
(
"/reset"
)
async
def
reset
(
):
lobby
.
reset
()
return
"Done."
app
.
include_router
(
router
)
...
...
server/game/lobby.py
View file @
24645257
...
...
@@ -15,6 +15,7 @@ class Metadata(BaseModel):
ready
:
bool
=
False
sid
:
str
=
""
last_announce
:
Optional
[
Announce
]
=
None
fresh_announce
:
bool
=
True
class
LobbyManager
(
ClientManager
):
...
...
@@ -46,9 +47,11 @@ class LobbyManager(ClientManager):
self
.
metadata
[
player
.
name
]
.
ready
=
True
print
(
f
"{player} ready to play! ({self.nb_ready} people ready)"
)
def
announces
(
self
,
player
:
Player
,
announce
:
Announce
):
async
def
announces
(
self
,
player
:
Player
,
announce
:
Announce
):
# FIXME: Call this message on incoming "ANNOUNCE"
self
.
metadata
[
player
.
name
]
.
last_announce
=
announce
meta
=
self
.
metadata
[
player
.
name
]
meta
.
last_announce
=
announce
meta
.
fresh_announce
=
True
print
(
f
"{player} ready to play! ({self.nb_ready} people ready)"
)
def
start_game
(
self
,
max_players
=
2
):
...
...
@@ -59,37 +62,63 @@ class LobbyManager(ClientManager):
print
(
f
"Game started : {' vs '.join([p.name for p in players])}"
)
def
reset
(
self
):
players
=
len
(
self
.
players
)
games
=
len
(
self
.
games
)
msg
=
f
"Resetting! sorry for the {players} players / {games} games..."
print
(
msg
)
for
p
in
self
.
players
:
m
=
self
.
metadata
[
p
.
name
]
self
.
send
(
p
,
MessageToPlayer
.
Reset
)
self
.
sio
.
disconnect
(
m
.
sid
)
self
.
lobby
.
clear
()
self
.
metadata
.
clear
()
self
.
games
.
clear
()
print
(
f
"Reset done, {players} players, {games} games."
)
return
msg
def
send
(
self
,
to
:
Player
,
message
:
MessageToPlayer
,
extra
=
None
):
data
=
{
message
:
message
.
name
}
if
extra
:
data
[
"extra"
]
=
extra
self
.
sio
.
send
(
message
)
pass
def
handle_message
(
self
,
sid
,
data
):
sender
:
Optional
[
Player
]
=
None
message
=
None
extras
=
None
extras
=
{
"players"
:
[
p
.
name
for
p
in
self
.
players
]}
print
(
f
"Lobby| Received message from {sid}: {data}."
)
for
name
,
metadata
in
self
.
metadata
.
items
():
if
metadata
.
sid
==
sid
:
sender
=
self
.
lobby
[
name
]
sender
=
self
.
which_player
(
sid
)
if
sender
:
print
(
f
"Lobby| Found sender: {sender.name}"
)
for
option
in
MessageFromPlayer
:
if
option
.
value
in
data
:
if
option
==
MessageFromPlayer
.
Waiting
:
self
.
metadata
[
sender
.
name
]
.
ready
=
False
extras
=
[
p
.
name
for
p
in
self
.
players
]
print
(
f
"MSG|Player ready {extras}"
)
print
(
f
"MSG|Player {sender.name} waiting, while {extras} ready."
)
if
len
(
self
.
players_ready
)
>
1
:
message
=
MessageToPlayer
.
ReadyToStart
elif
option
==
MessageFromPlayer
.
Ready
:
self
.
wants_to_play
(
player
=
sender
)
message
=
MessageToPlayer
.
Ready
extras
=
[
p
.
name
for
p
in
self
.
players_ready
]
print
(
f
"MSG|Players ready: {extras}."
)
message
=
MessageToPlayer
.
ReadyToStart
\
if
len
(
self
.
players_ready
)
>
1
else
MessageToPlayer
.
Waiting
extras
[
"playersReady"
]
=
[
p
.
name
for
p
in
self
.
players_ready
]
elif
option
==
MessageFromPlayer
.
Bet
:
# FIXME vraie annonce, pas juste carre d'as lol
self
.
announces
(
player
=
sender
,
announce
=
Announce
(
bet
=
CARRE_ACE
))
game
:
Game
=
self
.
game_with
(
sender
)
# TODO: connect with current game, return appropriate message
elif
option
==
MessageFromPlayer
.
Menteur
:
self
.
announces
(
player
=
sender
,
announce
=
Announce
())
...
...
@@ -99,3 +128,19 @@ class LobbyManager(ClientManager):
if
extras
:
body
[
"extras"
]
=
extras
return
json
.
dumps
(
body
)
def
which_player
(
self
,
sid
)
->
Player
:
sender
:
Optional
[
Player
]
=
None
for
name
,
metadata
in
self
.
metadata
.
items
():
if
metadata
.
sid
==
sid
:
sender
=
self
.
lobby
[
name
]
return
sender
def
game_with
(
self
,
player
:
Player
)
->
Game
:
return
[
g
for
g
in
self
.
games
if
player
in
g
.
players
][
0
]
def
send_waiting_for
(
self
,
player
:
Player
):
game
=
self
.
game_with
(
player
)
self
.
send
(
player
,
MessageToPlayer
.
YourTurn
,
extra
=
{
"bet"
:
game
.
current_bet
})
for
p
in
[
p
for
p
in
game
.
players
if
p
!=
player
]:
self
.
send
(
p
,
MessageToPlayer
.
Waiting
,
extra
=
{
"waitingFor"
:
p
.
name
})
server/game/message.py
View file @
24645257
...
...
@@ -3,7 +3,7 @@ from enum import Enum
class
MessageToPlayer
(
Enum
):
Waiting
=
"WAITING_ROOM"
Ready
=
"READY_ROOM"
Ready
ToStart
=
"READY_ROOM"
NewGame
=
"NEW_GAME"
GiveHand
=
"GIVE_HAND"
WaitTurn
=
"WAITING_TURN"
...
...
@@ -13,6 +13,7 @@ class MessageToPlayer(Enum):
Win
=
"WINNER"
WinnerIs
=
"WINNER_IS"
Lose
=
"LOSER"
Reset
=
"RESET"
class
MessageFromPlayer
(
Enum
):
...
...
server/model/card.py
View file @
24645257
...
...
@@ -48,6 +48,9 @@ class Card(BaseModel):
def
__str__
(
self
)
->
str
:
return
f
"{self.value.name} of {self.color.value if self.color else '?'}"
def
__hash__
(
self
)
->
int
:
return
hash
(
self
.
value
)
+
100
*
hash
(
self
.
color
)
def
score
(
self
)
->
int
:
return
int
(
self
.
value
.
value
)
...
...
server/model/game.py
View file @
24645257
...
...
@@ -37,13 +37,14 @@ class Game:
@property
def
global_hand
(
self
)
->
Hand
:
return
Hand
([
c
for
p
in
self
.
players
for
c
in
p
.
hand
])
all_cards
=
[
c
for
p
in
self
.
players
for
c
in
p
.
hand
.
cards
]
return
Hand
(
cards
=
all_cards
)
def
new_game
(
self
):
async
def
new_game
(
self
):
self
.
deck
.
reset
()
while
len
(
self
.
players
)
>
1
:
loser
=
self
.
new_turn
()
loser
=
await
self
.
new_turn
()
if
self
.
defeats
[
loser
]
==
5
:
print
(
f
"{loser} is eliminated!"
)
...
...
@@ -57,7 +58,7 @@ class Game:
self
.
message
(
MessageToPlayer
.
WinnerIs
,
extra
=
winner
)
print
(
f
"Game over - {winner.name} wins with {len(winner.hand)} cards!"
)
def
new_turn
(
self
)
->
Player
:
async
def
new_turn
(
self
)
->
Player
:
"""
Runs a turn of the game.
...
...
@@ -83,7 +84,7 @@ class Game:
self
.
current_bet
=
None
last_player
=
None
for
current_player
in
itertools
.
cycle
(
self
.
players
):
loser
=
self
.
play_turn
(
current_player
,
last_player
)
loser
=
await
self
.
play_turn
(
current_player
,
last_player
)
if
loser
is
not
None
:
self
.
players
.
remove
(
loser
)
self
.
players
.
insert
(
0
,
loser
)
...
...
@@ -100,8 +101,8 @@ class Game:
:return:
"""
to_scan
=
[
Card
(
c
.
value
)
for
c
in
self
.
global_hand
.
cards
.
copy
()]
# Keep only values
to_find
=
[
Card
(
c
.
value
)
for
c
in
bet
.
cards
]
to_scan
=
[
Card
(
value
=
c
.
value
)
for
c
in
self
.
global_hand
.
cards
.
copy
()]
# Keep only values
to_find
=
[
Card
(
value
=
c
.
value
)
for
c
in
bet
.
cards
]
menteur
=
False
for
card
in
to_find
:
...
...
@@ -130,7 +131,7 @@ class Game:
self
.
players
.
append
(
player
)
return
True
def
play_turn
(
self
,
current_player
:
Player
,
last_player
:
Player
)
->
Optional
[
Player
]:
async
def
play_turn
(
self
,
current_player
:
Player
,
last_player
:
Player
)
->
Optional
[
Player
]:
"""
Runs a turn, eventually returning a loser.
...
...
@@ -144,7 +145,7 @@ class Game:
self
.
message
(
MessageToPlayer
.
YourTurn
,
current_player
,
extra
=
self
.
current_bet
)
while
not
self
.
current_bet
:
# Ask a valid bet
# FIXME: Wait for player announce? Maybe just sleep 10?
announce
=
current_player
.
announce
(
self
.
current_bet
)
announce
=
await
current_player
.
announce
(
self
.
current_bet
)
if
announce
.
bet
:
self
.
current_bet
=
announce
.
bet
print
(
f
"{current_player} starts the round: {self.current_bet}"
)
...
...
server/model/players.py
View file @
24645257
from
abc
import
abstractmethod
,
ABC
from
dataclasses
import
dataclass
from
typing
import
Optional
from
pydantic.main
import
BaseModel
...
...
@@ -38,7 +37,7 @@ class Player(ABC):
self
.
hand
.
cards
.
clear
()
@abstractmethod
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
async
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
"""
Announces a bet or Menteur, based on the current bet.
:param current_bet:
...
...
@@ -54,7 +53,7 @@ class NaivePlayer(Player, ABC):
def
__str__
(
self
):
return
"Naive "
+
super
()
.
__str__
()
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
async
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
if
not
current_bet
or
self
.
hand
.
value
()
>
current_bet
.
value
():
return
Announce
(
bet
=
self
.
hand
)
else
:
...
...
@@ -67,7 +66,7 @@ class RandomPlayer(Player, ABC):
def
__str__
(
self
):
return
"Random "
+
super
()
.
__str__
()
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
async
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
if
current_bet
and
not
current_bet
.
is_menteur
and
current_bet
.
is_carre_as
:
return
Announce
()
else
:
...
...
@@ -80,6 +79,6 @@ class MenteurPlayer(Player, ABC):
def
__str__
(
self
):
return
"Menteur "
+
super
()
.
__str__
()
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
async
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
hand
=
random_option
()
return
Announce
(
bet
=
hand
)
server/test/test_data.py
View file @
24645257
...
...
@@ -185,14 +185,14 @@ class TestHand(TestCase):
# Full[Three of ♥|Three of ♣|Three of ♠|Four of ♥|Four of ♣]
# > Full[Two of ♥|Two of ♣|Two of ♠|Four of ♥|Four of ♣]]
full1
=
Hand
(
[
Card
(
v
)
for
v
in
[
full1
=
Hand
(
cards
=
[
Card
(
value
=
v
)
for
v
in
[
Value
.
Three
,
Value
.
Three
,
Value
.
Three
,
Value
.
Four
,
Value
.
Four
,
]])
full2
=
Hand
(
[
Card
(
v
)
for
v
in
[
full2
=
Hand
(
cards
=
[
Card
(
value
=
v
)
for
v
in
[
Value
.
Two
,
Value
.
Two
,
Value
.
Two
,
...
...
server/test/test_game.py
View file @
24645257
...
...
@@ -30,11 +30,11 @@ class TestGame(TestCase):
self
.
game
.
add_player
(
RandomPlayer
(
"Foo"
))
self
.
assertEqual
(
3
,
len
(
self
.
game
.
players
),
"Should not add duplicate"
)
def
test_turn
(
self
):
async
def
test_turn
(
self
):
menteur
=
MenteurPlayer
()
naive
=
NaivePlayer
()
self
.
game
=
Game
([
menteur
,
naive
])
self
.
game
.
new_turn
()
await
self
.
game
.
new_turn
()
self
.
assertEqual
(
1
,
len
([
v
for
v
in
self
.
game
.
defeats
.
values
()
if
v
>
0
]),
"There should have been one defeat."
)
self
.
assertTrue
(
1
in
self
.
game
.
defeats
.
values
(),
"A player should have one defeat."
)
...
...
@@ -42,8 +42,8 @@ class TestGame(TestCase):
loser
=
[
p
for
p
in
self
.
game
.
players
if
self
.
game
.
defeats
[
p
]][
0
]
self
.
assertEqual
(
self
.
game
.
players
[
0
],
loser
,
"The loser should be first to play."
)
def
test_full_game
(
self
):
self
.
game
.
new_game
()
async
def
test_full_game
(
self
):
await
self
.
game
.
new_game
()
self
.
assertGreater
(
len
(
self
.
game
.
defeats
.
values
()),
0
,
"There should be at least one player with defeats."
)
self
.
assertTrue
(
5
in
self
.
game
.
defeats
.
values
(),
"A player should have lost five times."
)
server/test/test_lobby.py
View file @
24645257
...
...
@@ -28,7 +28,7 @@ class MockPlayer(Player):
def
count
(
self
,
msg
:
MessageToPlayer
)
->
int
:
return
len
([
m
for
(
m
,
e
)
in
self
.
messages
if
m
is
msg
])
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
async
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
return
Announce
(
bet
=
self
.
bets
.
pop
()
if
self
.
bets
else
CARRE_ACE
)
def
receive
(
self
,
message
:
MessageToPlayer
,
extra
:
Optional
[
Any
]
=
None
):
...
...
@@ -62,15 +62,15 @@ class TestManager(TestCase):
deck
=
Deck
(
cards
=
cards
),
manager
=
self
.
manager
)
def
test_turn_messages
(
self
):
self
.
game
.
new_turn
()
async
def
test_turn_messages
(
self
):
await
self
.
game
.
new_turn
()
self
.
assertGot
(
self
.
j1
,
MessageToPlayer
.
LoseRound
,
extra
=
self
.
j1
.
name
,
msg
=
f
"J1 should hear round lost."
)
self
.
assertGot
(
self
.
j2
,
MessageToPlayer
.
LoseRound
,
extra
=
self
.
j1
.
name
,
msg
=
f
"J2 should hear round lost by j1."
)
for
player
in
[
self
.
j1
,
self
.
j2
]:
self
.
assertGot
(
player
,
MessageToPlayer
.
LoseRound
,
msg
=
"End of round not announced"
)
def
test_game_messages
(
self
):
self
.
game
.
new_game
()
async
def
test_game_messages
(
self
):
await
self
.
game
.
new_game
()
self
.
assertEqual
(
len
([
m
for
m
in
self
.
j1
.
messages
if
m
is
MessageToPlayer
.
LoseRound
]),
5
,
f
"{self.j1} should lose 5 rounds: {'|'.join([str(m) for m in self.j1.messages])}"
)
...
...
server/ws.py
View file @
24645257
from
time
import
sleep
from
typing
import
Optional
import
socketio
from
server.game.lobby
import
LobbyManager
from
server.game.message
import
MessageToPlayer
from
server.model.hand
import
Hand
from
server.model.players
import
Player
,
Announce
...
...
@@ -21,6 +23,10 @@ class ClientPlayer(Player):
self
.
ready
=
False
async
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
announce
=
None
while
not
self
.
lobby
.
metadata
[
self
.
name
]
.
fresh_announce
:
lobby
.
send_waiting_for
(
self
)
sleep
(
2
)
return
lobby
.
last_announces
[
self
]
...
...
@@ -47,3 +53,4 @@ async def ping_server(sid, data):
@sio.event
def
disconnect
(
sid
):
print
(
'[WS] Disconnect '
,
sid
)
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