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
118e40b2
Unverified
Commit
118e40b2
authored
Apr 05, 2020
by
PLN (Algolia)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat/refactor/fix: Players, turn, deals
parent
1ad38c08
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
270 additions
and
104 deletions
+270
-104
__init__.py
server/__init__.py
+0
-0
card.py
server/model/card.py
+10
-2
data.py
server/model/data.py
+78
-61
deck.py
server/model/deck.py
+1
-2
hand.py
server/model/hand.py
+18
-3
hands.py
server/model/hands.py
+33
-1
players.py
server/model/players.py
+84
-0
test_data.py
server/test/test_data.py
+30
-32
test_game.py
server/test/test_game.py
+16
-3
No files found.
server/__init__.py
0 → 100644
View file @
118e40b2
server/model/card.py
View file @
118e40b2
...
...
@@ -49,4 +49,12 @@ class Card:
return
f
"{self.value.name} of {self.color.value if self.color else '?'}"
def
score
(
self
)
->
int
:
return
int
(
self
.
value
.
value
)
\ No newline at end of file
return
int
(
self
.
value
.
value
)
def
lowest_value_and_rest
():
lowValue
:
Value
=
Value
.
Two
otherValues
=
list
(
Value
)
otherValues
.
remove
(
lowValue
)
return
lowValue
,
otherValues
\ No newline at end of file
server/model/data.py
View file @
118e40b2
import
itertools
from
collections
import
defaultdict
from
dataclasses
import
dataclass
from
typing
import
List
,
Dict
,
Optional
from
server.model.hand
import
Hand
from
server.model.animals
import
random_animal_name
from
server.model.card
import
Card
from
server.model.deck
import
Deck
@dataclass
class
Announce
:
bet
:
Optional
[
Hand
]
=
None
@property
def
menteur
(
self
):
return
not
self
.
bet
class
Player
:
def
__init__
(
self
,
name
:
str
=
None
):
if
not
name
:
name
=
random_animal_name
()
self
.
name
:
str
=
name
self
.
hand
:
Hand
=
Hand
()
def
__str__
(
self
):
return
f
"Player {self.name}"
def
give
(
self
,
card
:
Card
):
self
.
hand
.
give
(
card
)
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
""" A naive player that only trusts what they sees:
bets his hand, or menteur if his hand is lower. """
if
not
current_bet
or
self
.
hand
.
value
()
>
current_bet
.
value
():
return
Announce
(
self
.
hand
)
else
:
return
Announce
()
from
server.model.hand
import
Hand
from
server.model.players
import
Player
class
Game
:
...
...
@@ -48,6 +16,7 @@ class Game:
self
.
deck
:
Deck
=
deck
self
.
players
:
List
[
Player
]
=
players
self
.
defeats
:
Dict
[
Player
,
int
]
=
defaultdict
(
lambda
:
0
)
self
.
current_bet
:
Optional
[
Hand
]
=
None
@property
def
global_hand
(
self
)
->
Hand
:
...
...
@@ -64,6 +33,8 @@ class Game:
self
.
players
.
remove
(
loser
)
else
:
print
(
f
"{loser} lost the round, now playing with {self.defeats[loser] + 1} cards!"
)
self
.
players
.
remove
(
loser
)
self
.
players
.
insert
(
0
,
loser
)
winner
=
self
.
players
[
0
]
print
(
f
"Game over - {winner.name} wins with {len(winner.hand)} cards!"
)
...
...
@@ -77,49 +48,95 @@ class Game:
# Distribution
self
.
deck
.
reset
()
for
current_player
in
self
.
players
:
current_player
.
give
(
self
.
deck
.
random_card
())
current_player
.
clear
()
draw
=
[]
for
i
in
range
(
self
.
defeats
[
current_player
]
+
1
):
draw
.
append
(
self
.
deck
.
random_card
())
print
(
f
"Drew {draw} for {current_player}."
)
for
d
in
draw
:
current_player
.
give
(
d
)
# Tour
current_bet
:
Optional
[
Hand
]
=
None
self
.
current_bet
=
None
last_player
=
None
for
current_player
in
itertools
.
cycle
(
self
.
players
):
print
(
f
"| {current_player}'s turn >"
)
if
not
current_bet
:
# First player, has to bet something
while
not
current_bet
:
announce
=
current_player
.
announce
(
current_bet
)
if
announce
.
bet
:
current_bet
=
announce
.
bet
print
(
f
"{current_player} starts the round: {current_bet}"
)
else
:
# Next player, announce or menteur
announce
=
current_player
.
announce
(
current_bet
)
if
announce
.
bet
:
while
announce
.
bet
.
value
()
<
current_bet
.
value
():
# Bad announce!
print
(
f
"Invalid bet, {announce.bet} < {current_bet}!"
)
announce
=
current_player
.
announce
(
current_bet
)
current_bet
=
announce
.
bet
print
(
f
" {current_player} bets {current_bet}."
)
else
:
# Menteur! Who lost the round?
menteur
=
self
.
is_menteur
(
current_bet
)
print
(
f
"{current_player} says Menteur... {'Bravo!' if menteur else 'FAIL!'}"
)
loser
=
last_player
if
menteur
else
current_player
print
(
f
"{loser} lost the round!"
)
self
.
count_defeat
(
loser
)
return
loser
loser
=
self
.
play_turn
(
current_player
,
last_player
)
if
loser
is
not
None
:
return
loser
last_player
=
current_player
# TODO: Put next first player first of list
def
is_menteur
(
self
,
bet
:
Hand
):
"""
Is this bet a menteur?
:param bet:
: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
]
menteur
=
False
for
card
in
to_find
:
if
card
in
to_scan
:
to_scan
.
remove
(
card
)
continue
else
:
print
(
f
"Missing a {card}!"
)
print
(
f
"Didn't find {bet} in {self.global_hand}: MENTEUR!"
)
return
False
#
print
(
f
"Missing {card}!"
)
menteur
=
True
if
menteur
:
print
(
f
"Didn't find {bet} in {self.global_hand}: MENTEUR!"
)
return
menteur
def
count_defeat
(
self
,
loser
):
self
.
defeats
[
loser
]
+=
1
def
add_player
(
self
,
player
:
Player
):
"""
Adds a player to the game.
:return: True if the player was accepted (unique names).
"""
if
any
([
s
.
name
==
player
.
name
for
s
in
self
.
players
]):
return
False
else
:
self
.
players
.
append
(
player
)
return
True
def
play_turn
(
self
,
current_player
:
Player
,
last_player
:
Player
)
->
Optional
[
Player
]:
"""
Runs a turn, eventually returning a loser.
:param current_player:
:param last_player:
:return:
"""
print
(
f
"| {current_player}'s turn >"
)
if
not
self
.
current_bet
:
# First player, has to bet something
while
not
self
.
current_bet
:
# Ask a valid bet
announce
=
current_player
.
announce
(
self
.
current_bet
)
if
announce
.
bet
:
self
.
current_bet
=
announce
.
bet
print
(
f
"{current_player} starts the round: {self.current_bet}"
)
else
:
# Next player, announce or menteur
announce
=
current_player
.
announce
(
self
.
current_bet
)
if
announce
.
bet
:
while
announce
.
bet
.
value
()
<
self
.
current_bet
.
value
():
# Bad announce!
print
(
f
"Invalid bet by {current_player}, {announce.bet} < {self.current_bet}!"
)
announce
=
current_player
.
announce
(
self
.
current_bet
)
self
.
current_bet
=
announce
.
bet
# Valid bet:
print
(
f
" {current_player} bets {self.current_bet}."
)
else
:
# Menteur! Who lost the round?
print
(
f
" {current_player} says Menteur!"
)
menteur
=
self
.
is_menteur
(
self
.
current_bet
)
loser
=
last_player
if
menteur
else
current_player
self
.
count_defeat
(
loser
)
print
(
f
"{current_player} says Menteur... {'Bravo!' if menteur else 'FAIL!'}"
)
return
loser
server/model/deck.py
View file @
118e40b2
...
...
@@ -22,4 +22,4 @@ class Deck:
def
reset
(
self
):
self
.
cards
.
extend
(
self
.
defausse
)
self
.
defausse
.
clear
()
shuffle
(
self
.
cards
)
\ No newline at end of file
shuffle
(
self
.
cards
)
server/model/hand.py
View file @
118e40b2
from
collections
import
Counter
from
typing
import
List
from
server.model.card
import
Card
from
server.model.card
import
Card
,
Value
class
Hand
:
...
...
@@ -23,9 +23,17 @@ class Hand:
return
"|"
.
join
([
str
(
c
)
for
c
in
self
.
cards
])
def
give
(
self
,
other
:
Card
):
"""
Adds another card to this hand.
:raises ValueError if the card is already in this hand.
"""
if
(
other
.
value
,
other
.
color
)
in
[(
c
.
value
,
c
.
color
)
for
c
in
self
.
cards
]:
raise
ValueError
(
f
"TRICHEUR! {other} already in this hand: {self.cards}!"
)
self
.
cards
.
append
(
other
)
def
value
(
self
):
""" Scores this hand according to the Poker Menteur rules."""
counter
=
Counter
([
c
.
value
for
c
in
self
.
cards
])
has_pair
=
False
...
...
@@ -87,4 +95,12 @@ class Hand:
analysis_log
+=
f
"
\t
-> score=
\t
{score}"
# print(analysis_log)
return
score
\ No newline at end of file
return
score
@property
def
is_menteur
(
self
)
->
bool
:
return
len
(
self
.
cards
)
==
0
@property
def
is_carre_as
(
self
)
->
bool
:
return
len
(
self
.
cards
)
==
4
and
all
([
c
.
value
==
Value
.
Ace
for
c
in
self
.
cards
])
server/model/hands.py
View file @
118e40b2
from
server.model.hand
import
Hand
from
random
import
choice
from
typing
import
List
from
server.model.card
import
Value
,
Color
,
Card
from
server.model.hand
import
Hand
def
carre
(
value
)
->
Hand
:
...
...
@@ -57,3 +60,32 @@ def double_pair(value: Value, other: Value = None):
Card
(
other
,
Color
.
Hearts
),
Card
(
other
,
Color
.
Clubs
)
])
def
all_options
()
->
List
[
Hand
]:
hands
=
[]
carres
=
[
carre
(
value
)
for
value
in
Value
]
brelans
=
[
brelan
(
value
)
for
value
in
Value
]
pairs
=
[
pair
(
value
)
for
value
in
Value
]
singles
=
[
single
(
value
)
for
value
in
Value
]
hands
.
extend
(
carres
)
hands
.
extend
(
brelans
)
hands
.
extend
(
pairs
)
hands
.
extend
(
singles
)
for
b
in
brelans
:
for
p
in
pairs
:
if
any
([
c
in
b
.
cards
for
c
in
p
.
cards
]):
# Invalid full
print
(
f
"Invalid full: {b.cards}-{p.cards}"
)
else
:
hands
.
append
(
Hand
([
*
b
.
cards
,
*
p
.
cards
]))
return
hands
options
=
all_options
()
def
random_option
()
->
Hand
:
return
choice
(
options
)
server/model/players.py
0 → 100644
View file @
118e40b2
from
abc
import
abstractmethod
,
ABC
from
dataclasses
import
dataclass
from
typing
import
Optional
from
server.model.animals
import
random_animal_name
from
server.model.card
import
Card
from
server.model.hand
import
Hand
from
server.model.hands
import
random_option
@dataclass
class
Announce
:
bet
:
Optional
[
Hand
]
=
None
@property
def
menteur
(
self
):
return
not
self
.
bet
class
Player
(
ABC
):
def
__init__
(
self
,
name
:
str
=
None
):
if
not
name
:
name
=
random_animal_name
()
self
.
name
:
str
=
name
self
.
hand
:
Hand
=
Hand
()
def
__str__
(
self
):
return
f
"Player {self.name}"
def
__repr__
(
self
):
return
str
(
self
)
def
give
(
self
,
card
:
Card
):
self
.
hand
.
give
(
card
)
def
clear
(
self
):
self
.
hand
.
cards
.
clear
()
@abstractmethod
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
"""
Announces a bet or Menteur, based on the current bet.
:param current_bet:
:return:
"""
return
Announce
()
class
NaivePlayer
(
Player
,
ABC
):
""" A naive player that only trusts what they sees:
bets his hand, or menteur if his hand is lower. """
def
__str__
(
self
):
return
"Naive "
+
super
()
.
__str__
()
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
if
not
current_bet
or
self
.
hand
.
value
()
>
current_bet
.
value
():
return
Announce
(
self
.
hand
)
else
:
return
Announce
()
class
RandomPlayer
(
Player
,
ABC
):
""" A weird player that never says menteur, always betting a random option. """
def
__str__
(
self
):
return
"Random "
+
super
()
.
__str__
()
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
:
return
Announce
(
random_option
())
class
MenteurPlayer
(
Player
,
ABC
):
""" A crazy player that always says menteur. """
def
__str__
(
self
):
return
"Menteur "
+
super
()
.
__str__
()
def
announce
(
self
,
current_bet
:
Optional
[
Hand
])
->
Announce
:
hand
=
random_option
()
return
Announce
(
hand
)
server/test/test_data.py
View file @
118e40b2
from
unittest
import
TestCase
from
server.model.
data
import
Player
from
server.model.
card
import
Value
,
Color
,
Card
,
lowest_value_and_rest
from
server.model.deck
import
Deck
from
server.model.hand
import
Hand
from
server.model.card
import
Value
,
Color
,
Card
from
server.model.hands
import
full
,
brelan
,
pair
,
single
,
double_pair
,
carre
from
server.model.known
import
ACE_OF_HEARTS
,
PAIR_ACE
,
SINGLE_ACE
,
DOUBLE_PAIR_ACE
,
BRELAN_ACE
,
FULL_ACE
from
server.model.players
import
RandomPlayer
class
TestDeck
(
TestCase
):
...
...
@@ -30,7 +30,7 @@ class TestPlayer(TestCase):
def
setUp
(
self
)
->
None
:
super
()
.
setUp
()
self
.
player
=
Player
()
self
.
player
=
Random
Player
()
def
testHand
(
self
):
self
.
assertEqual
(
0
,
len
(
self
.
player
.
hand
),
"Begin no cards"
)
...
...
@@ -42,18 +42,15 @@ class TestPlayer(TestCase):
self
.
assertEqual
(
Value
.
Ace
,
self
.
player
.
hand
[
0
]
.
value
,
"Is Ace"
)
self
.
assertEqual
(
Color
.
Hearts
,
self
.
player
.
hand
[
0
]
.
color
,
"of Hearts"
)
def
testTricheur
(
self
):
with
self
.
assertRaises
(
ValueError
,
msg
=
"Giving someone a card they already have is an error"
):
self
.
player
.
give
(
ACE_OF_HEARTS
)
self
.
player
.
give
(
ACE_OF_HEARTS
)
def
testDefeats
(
self
):
pass
def
lowest_value_and_rest
():
lowValue
:
Value
=
Value
.
Two
otherValues
=
list
(
Value
)
otherValues
.
remove
(
lowValue
)
return
lowValue
,
otherValues
class
TestHand
(
TestCase
):
def
setUp
(
self
)
->
None
:
self
.
hand
=
Hand
()
...
...
@@ -126,27 +123,6 @@ class TestHand(TestCase):
self
.
assertGreater
(
high_score
,
pair_score
,
f
"Brelan[{high_hand}] > Pair[Ace]"
)
self
.
assertGreater
(
high_score
,
single_score
,
f
"Brelan[{high_hand}] > Ace"
)
def
testFulls
(
self
):
# AssertionError: 24060 not greater than 24060 :
# 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
[
Value
.
Three
,
Value
.
Three
,
Value
.
Three
,
Value
.
Four
,
Value
.
Four
,
]])
full2
=
Hand
([
Card
(
v
)
for
v
in
[
Value
.
Two
,
Value
.
Two
,
Value
.
Two
,
Value
.
Four
,
Value
.
Four
,
]])
self
.
assertGreater
(
full1
.
value
(),
full2
.
value
(),
"Full1 > full2 (threes >> twos)"
)
def
testFull
(
self
):
low_value
,
other_values
=
lowest_value_and_rest
()
...
...
@@ -202,3 +178,25 @@ class TestHand(TestCase):
self
.
assertGreater
(
high_score
,
double_pair_score
,
f
"Carre[{high_hand}] > Pair[Ace]"
)
self
.
assertGreater
(
high_score
,
pair_score
,
f
"Carre[{high_hand}] > Pair[Ace]"
)
self
.
assertGreater
(
high_score
,
single_score
,
f
"Carre[{high_hand}] > Ace"
)
# Specifics
def
testFulls
(
self
):
# AssertionError: 24060 not greater than 24060 :
# 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
[
Value
.
Three
,
Value
.
Three
,
Value
.
Three
,
Value
.
Four
,
Value
.
Four
,
]])
full2
=
Hand
([
Card
(
v
)
for
v
in
[
Value
.
Two
,
Value
.
Two
,
Value
.
Two
,
Value
.
Four
,
Value
.
Four
,
]])
self
.
assertGreater
(
full1
.
value
(),
full2
.
value
(),
"Full1 > full2 (threes >> twos)"
)
server/test/test_game.py
View file @
118e40b2
from
unittest
import
TestCase
from
server.model.data
import
Game
,
Player
from
server.model.data
import
Game
from
server.model.players
import
MenteurPlayer
,
NaivePlayer
,
RandomPlayer
class
TestGame
(
TestCase
):
def
setUp
(
self
)
->
None
:
super
()
.
setUp
()
self
.
player1
=
Player
(
"PLN"
)
self
.
player2
=
Player
(
"Nassim"
)
self
.
player1
=
Random
Player
(
"PLN"
)
self
.
player2
=
Naive
Player
(
"Nassim"
)
self
.
game
=
Game
([
self
.
player1
,
self
.
player2
])
def
test_global_hand
(
self
)
->
None
:
...
...
@@ -23,11 +24,23 @@ class TestGame(TestCase):
self
.
assertTrue
(
card2
in
self
.
game
.
global_hand
,
"Global hand should contain player2's first card"
)
self
.
assertTrue
(
card3
in
self
.
game
.
global_hand
,
"Global hand should contain player2's second card"
)
def
test_add_player
(
self
):
self
.
game
.
add_player
(
RandomPlayer
(
"Foo"
))
self
.
assertEqual
(
3
,
len
(
self
.
game
.
players
),
"Should be added"
)
self
.
game
.
add_player
(
RandomPlayer
(
"Foo"
))
self
.
assertEqual
(
3
,
len
(
self
.
game
.
players
),
"Should not add duplicate"
)
def
test_turn
(
self
):
menteur
=
MenteurPlayer
()
naive
=
NaivePlayer
()
self
.
game
=
Game
([
menteur
,
naive
])
self
.
game
.
new_turn
()
self
.
assertEqual
(
1
,
len
(
self
.
game
.
defeats
.
values
()),
"There should have been one defeat."
)
self
.
assertTrue
(
1
in
self
.
game
.
defeats
.
values
(),
"A player should have one defeat."
)
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
()
...
...
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