Merge pull request #12 from albertoxamin/dev

general fixes and 2 new characters
This commit is contained in:
Alberto Xamin 2020-12-13 17:02:35 +01:00 committed by GitHub
commit 89b241e083
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 128 additions and 35 deletions

10
.github/workflows/tests.yaml vendored Normal file
View File

@ -0,0 +1,10 @@
name: Test Pull requests
on:
pull_request:
jobs:
test_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Unified Docker image
run: docker build . --file Dockerfile

View File

@ -173,13 +173,14 @@ def chat_message(sid, msg):
elif '/cancelgame' in msg and ses.game.started: elif '/cancelgame' in msg and ses.game.started:
ses.game.reset() ses.game.reset()
elif '/gameinfo' in msg: elif '/gameinfo' in msg:
sio.emit('chat_message', room=sid, data=f'info: {ses.game.__dict__}') sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'info: {ses.game.__dict__}'})
elif '/meinfo' in msg: elif '/meinfo' in msg:
sio.emit('chat_message', room=sid, data=f'info: {ses.__dict__}') sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'info: {ses.__dict__}'})
else: else:
sio.emit('chat_message', room=sid, data=f'{msg} COMMAND NOT FOUND') sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'{msg} COMMAND NOT FOUND'})
else: else:
sio.emit('chat_message', room=ses.game.name, data=f'[{ses.name}]: {msg}') color = sid.encode('utf-8').hex()[-3:]
sio.emit('chat_message', room=ses.game.name, data={'color': f'#{color}','text':f'[{ses.name}]: {msg}'})
@sio.event @sio.event
def start_game(sid): def start_game(sid):

View File

@ -7,9 +7,12 @@ class Deck:
super().__init__() super().__init__()
self.cards: List[cs.Card] = cs.get_starting_deck(game.expansions) self.cards: List[cs.Card] = cs.get_starting_deck(game.expansions)
self.mancato_cards: List[str] = [] self.mancato_cards: List[str] = []
self.mancato_cards_not_green: List[str] = []
for c in self.cards: for c in self.cards:
if isinstance(c, cs.Mancato) and c.name not in self.mancato_cards: if isinstance(c, cs.Mancato) and c.name not in self.mancato_cards:
self.mancato_cards.append(c.name) self.mancato_cards.append(c.name)
if not c.usable_next_turn:
self.mancato_cards_not_green.append(c.name)
self.all_cards_str: List[str] = [] self.all_cards_str: List[str] = []
for c in self.cards: for c in self.cards:
if c.name not in self.all_cards_str: if c.name not in self.all_cards_str:

View File

@ -66,7 +66,7 @@ class Rissa(CatBalou):
self.name = 'Rissa' self.name = 'Rissa'
self.icon = '🥊' self.icon = '🥊'
self.desc = "Fai scartare una carta a tutti gli altri giocatori, scegli a caso dalla mano, oppure fra quelle che hanno in gioco" self.desc = "Fai scartare una carta a tutti gli altri giocatori, scegli a caso dalla mano, oppure fra quelle che hanno in gioco"
self.desc_eng = "Choose a card to discard from the hand/equipment ofc all the other players" self.desc_eng = "Choose a card to discard from the hand/equipment of all the other players"
self.need_with = True self.need_with = True
self.need_target = False self.need_target = False
self.alt_text = '2🃏' self.alt_text = '2🃏'

View File

@ -57,6 +57,20 @@ class ApacheKid(Character):
self.desc_eng = "Cards of diamonds ♦️ played against him, do no have effect (doesn't work in duels)." self.desc_eng = "Cards of diamonds ♦️ played against him, do no have effect (doesn't work in duels)."
self.icon = '♦️' self.icon = '♦️'
class SeanMallory(Character):
def __init__(self):
super().__init__("Sean Mallory", max_lives=3)
self.desc = "Quando finisce il suo turno può tenere fino a 10 carte in mano"
self.desc_eng = "He can keep up to 10 cards in his hand when ending the turn."
self.icon = '🍟'
class BelleStar(Character):
def __init__(self):
super().__init__("Belle Star", max_lives=3)
self.desc = "Nel suo turno le carte verdi degli altri giocatori non hanno effetto."
self.desc_eng = "During her turn the green cards of the other players do not work."
self.icon = ''
def all_characters() -> List[Character]: def all_characters() -> List[Character]:
return [ return [
PixiePete(), PixiePete(),
@ -67,6 +81,8 @@ def all_characters() -> List[Character]:
BillNoface(), BillNoface(),
MollyStark(), MollyStark(),
ApacheKid(), ApacheKid(),
SeanMallory(),
BelleStar(),
] ]
#Apache Kid: il suo effetto non conta nei duelli #Apache Kid: il suo effetto non conta nei duelli

View File

@ -233,7 +233,8 @@ class Player:
self.play_card(len(self.hand)+i, against=target['name']) self.play_card(len(self.hand)+i, against=target['name'])
has_played = True has_played = True
break break
if not has_played and len(self.hand) > self.lives: maxcards = self.lives if not isinstance(self.character, chd.SeanMallory) else 10
if not has_played and len(self.hand) > maxcards:
self.scrap(0) self.scrap(0)
else: else:
self.end_turn() self.end_turn()
@ -507,6 +508,8 @@ class Player:
print('has mancato') print('has mancato')
self.pending_action = PendingAction.RESPOND self.pending_action = PendingAction.RESPOND
self.expected_response = self.game.deck.mancato_cards self.expected_response = self.game.deck.mancato_cards
if self.attacker and isinstance(self.attacker.character, chd.BelleStar):
self.expected_response = self.game.deck.mancato_cards_not_green
if isinstance(self.character, chd.ElenaFuente): if isinstance(self.character, chd.ElenaFuente):
self.expected_response = self.game.deck.all_cards_str self.expected_response = self.game.deck.all_cards_str
self.on_failed_response_cb = self.take_damage_response self.on_failed_response_cb = self.take_damage_response
@ -575,7 +578,7 @@ class Player:
((hand_index < len(self.hand) and self.hand[hand_index].name in self.expected_response)) or ((hand_index < len(self.hand) and self.hand[hand_index].name in self.expected_response)) or
self.equipment[hand_index-len(self.hand)].name in self.expected_response): self.equipment[hand_index-len(self.hand)].name in self.expected_response):
card = self.hand.pop(hand_index) if hand_index < len(self.hand) else self.equipment.pop(hand_index-len(self.hand)) card = self.hand.pop(hand_index) if hand_index < len(self.hand) else self.equipment.pop(hand_index-len(self.hand))
if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand) and not self.is_my_turn and self.event_type != 'duel': if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand)+1 and not self.is_my_turn and self.event_type != 'duel':
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
card.use_card(self) card.use_card(self)
self.game.deck.scrap(card) self.game.deck.scrap(card)
@ -584,7 +587,7 @@ class Player:
if self.mancato_needed <= 0: if self.mancato_needed <= 0:
if self.event_type == 'duel': if self.event_type == 'duel':
self.game.duel(self, self.attacker.name) self.game.duel(self, self.attacker.name)
if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand) and not self.is_my_turn: if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand)+1 and not self.is_my_turn:
self.molly_discarded_cards += 1 self.molly_discarded_cards += 1
else: else:
self.game.responders_did_respond_resume_turn() self.game.responders_did_respond_resume_turn()
@ -640,7 +643,8 @@ class Player:
def end_turn(self, forced=False): def end_turn(self, forced=False):
if not self.is_my_turn: if not self.is_my_turn:
return return
if len(self.hand) > self.max_lives and not forced: maxcards = self.lives if not isinstance(self.character, chd.SeanMallory) else 10
if len(self.hand) > maxcards and not forced:
print( print(
f"I {self.name} have to many cards in my hand and I can't end the turn") f"I {self.name} have to many cards in my hand and I can't end the turn")
elif self.pending_action == PendingAction.PLAY or forced: elif self.pending_action == PendingAction.PLAY or forced:

View File

@ -50,7 +50,7 @@ class Vice(Role):
class Outlaw(Role): class Outlaw(Role):
def __init__(self, alternative_goal=None, alternative_goal_eng=None): def __init__(self, alternative_goal=None, alternative_goal_eng=None):
super().__init__("Fuorilegge", "Elimina lo Sceriffo!") super().__init__("Fuorilegge", "Elimina lo Sceriffo!")
self.goal_eng = "Kill the sSheriff!" self.goal_eng = "Kill the Sheriff!"
if alternative_goal: if alternative_goal:
self.goal = alternative_goal self.goal = alternative_goal
self.goal_eng = alternative_goal_eng self.goal_eng = alternative_goal_eng

View File

@ -2,7 +2,7 @@
<div class="chat"> <div class="chat">
<h3>{{$t("chat.chat")}}</h3> <h3>{{$t("chat.chat")}}</h3>
<div id="chatbox"> <div id="chatbox">
<p style="margin:1pt;" class="chat-message" v-for="msg in messages" v-bind:key="msg">{{msg}}</p> <p style="margin:1pt;" class="chat-message" v-for="msg in messages" v-bind:key="msg" :style="`color:${msg.color}`">{{msg.text}}</p>
<p class="end">.</p> <p class="end">.</p>
</div> </div>
<form @submit="sendChatMessage" id="msg-form"> <form @submit="sendChatMessage" id="msg-form">
@ -21,10 +21,11 @@ export default {
}), }),
sockets: { sockets: {
chat_message(msg) { chat_message(msg) {
if (msg.indexOf('_') === 0) { console.log(msg)
if ((typeof msg === "string") && msg.indexOf('_') === 0) {
let params = msg.split('|') let params = msg.split('|')
let type = params.shift().substring(1) let type = params.shift().substring(1)
this.messages.push(this.$t(`chat.${type}`, params)) this.messages.push({text:this.$t(`chat.${type}`, params)})
}else { }else {
this.messages.push(msg) this.messages.push(msg)
} }
@ -45,8 +46,8 @@ export default {
</script> </script>
<style scoped> <style scoped>
#chatbox { #chatbox {
flex: 1;
width:100%; width:100%;
max-height:150px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -61,14 +62,27 @@ input {
height: 0pt; height: 0pt;
margin-top: -1.5pt; margin-top: -1.5pt;
} }
.chat {
display: flex;
flex-direction: column;
}
#msg-form { #msg-form {
width:100%; width:100%;
padding:0; padding:0;
display:flex; display:flex;
} }
@media only screen and (min-width:1000px) {
.chat {
height: 90vh;
margin-left: 10pt;
}
}
@media only screen and (max-width:1000px) { @media only screen and (max-width:1000px) {
#msg-form { #msg-form {
flex-direction: column; flex-direction: column;
} }
#chatbox {
max-height:150px;
}
} }
</style> </style>

View File

@ -8,7 +8,7 @@
<card :card="card" :class="{back:true, pick:pending_action === 0, draw:pending_action === 1}" @click.native="action"/> <card :card="card" :class="{back:true, pick:pending_action === 0, draw:pending_action === 1}" @click.native="action"/>
</div> </div>
<div style="position:relative;"> <div style="position:relative;">
<card v-if="previousScrap" :card="previousScrap"/> <card v-if="previousScrap" :card="previousScrap" style="top: 1.5pt;right: -1.5pt;"/>
<card v-else :card="card" class="back" style="opacity:0"/> <card v-else :card="card" class="back" style="opacity:0"/>
<card v-if="lastScrap" :card="lastScrap" :key="lastScrap" class="last-scrap" @click.native="action('scrap')" <card v-if="lastScrap" :card="lastScrap" :key="lastScrap" class="last-scrap" @click.native="action('scrap')"
@pointerenter.native="desc=($i18n.locale=='it'?lastScrap.desc:lastScrap.desc_eng)" @pointerleave.native="desc=''" /> @pointerenter.native="desc=($i18n.locale=='it'?lastScrap.desc:lastScrap.desc_eng)" @pointerleave.native="desc=''" />
@ -93,6 +93,10 @@ export default {
animation-duration: 0.8s; animation-duration: 0.8s;
animation-name: slidein; animation-name: slidein;
} }
.last-scrap:hover {
opacity: 0.8;
transform: translateY(-10px);
}
@keyframes slidein { @keyframes slidein {
from { from {
transform: translate(-100px, 10px) scale(1.3) rotate(-10deg); transform: translate(-100px, 10px) scale(1.3) rotate(-10deg);

View File

@ -1,7 +1,10 @@
<template> <template>
<div class="lobby"> <div class="lobby">
<div style="flex-grow: 4;"> <div style="flex-grow: 4;">
<h2 v-if="!started">{{$t('room')}}{{ lobbyName }}</h2> <div style="position:relative;">
<h2 v-if="!started">{{$t('room')}}{{ lobbyName }}</h2>
<input style="position:absolute;top:0;right:0;max-height:100pt" v-if="!started" type="button" @click="leaveRoom" :value="$t('leave_room')"/>
</div>
<h3>{{$t('room_players', {username:username})}}</h3> <h3>{{$t('room_players', {username:username})}}</h3>
<div v-if="!started"> <div v-if="!started">
<PrettyCheck v-if="isRoomOwner" class="p-switch p-fill" v-model="privateRoom" style="margin-top:5px; margin-bottom:3px;">{{$t("private_room")}}</PrettyCheck> <PrettyCheck v-if="isRoomOwner" class="p-switch p-fill" v-model="privateRoom" style="margin-top:5px; margin-bottom:3px;">{{$t("private_room")}}</PrettyCheck>
@ -19,7 +22,7 @@
</transition-group> </transition-group>
<Card :card="p.card" :class="{is_my_turn:p.is_my_turn}"/> <Card :card="p.card" :class="{is_my_turn:p.is_my_turn}"/>
<Card v-if="p.character" :card="p.character" class="character tiny-character" @click.native="selectedInfo = [p.character]"/> <Card v-if="p.character" :card="p.character" class="character tiny-character" @click.native="selectedInfo = [p.character]"/>
<tiny-hand :ncards="p.ncards" @click.native="drawFromPlayer(p.name)"/> <tiny-hand :ncards="p.ncards" @click.native="drawFromPlayer(p.name)" :ismyturn="p.pending_action === 2"/>
<span style="position:absolute;top:10pt;" class="center-stuff">{{getActionEmoji(p)}}</span> <span style="position:absolute;top:10pt;" class="center-stuff">{{getActionEmoji(p)}}</span>
<div class="tiny-equipment"> <div class="tiny-equipment">
<Card v-for="card in p.equipment" v-bind:key="card.name+card.number" :card="card" @click.native="selectedInfo = p.equipment"/> <Card v-for="card in p.equipment" v-bind:key="card.name+card.number" :card="card" @click.native="selectedInfo = p.equipment"/>
@ -165,6 +168,9 @@ export default {
} }
}, },
methods: { methods: {
leaveRoom() {
window.location.replace(window.location.origin)
},
toggleExpansions(name) { toggleExpansions(name) {
if (!this.isRoomOwner) return; if (!this.isRoomOwner) return;
this.$socket.emit('toggle_expansion', name) this.$socket.emit('toggle_expansion', name)
@ -257,8 +263,8 @@ export default {
.is_my_turn { .is_my_turn {
box-shadow: 0 0 0 3pt rgb(138, 12, 12), 0 0 0 6pt white, 0 0 5pt 6pt #aaa !important; box-shadow: 0 0 0 3pt rgb(138, 12, 12), 0 0 0 6pt white, 0 0 5pt 6pt #aaa !important;
animation-name: turn-animation; animation-name: turn-animation;
animation-duration: 2s; animation-duration: 2s;
animation-iteration-count: infinite; animation-iteration-count: infinite;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.is_my_turn { .is_my_turn {
@ -270,7 +276,7 @@ export default {
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.05); transform: scale(1.02);
} }
100% { 100% {
transform: scale(1); transform: scale(1);

View File

@ -20,8 +20,8 @@
<transition name="list"> <transition name="list">
<p v-if="desc"><i>{{desc}}</i></p> <p v-if="desc"><i>{{desc}}</i></p>
</transition> </transition>
<div v-if="lives > 0"> <div v-if="lives > 0" style="position:relative">
<span>{{$t('hand')}}</span> <span id="hand_text">{{$t('hand')}}</span>
<transition-group name="list" tag="div" class="hand"> <transition-group name="list" tag="div" class="hand">
<Card v-for="card in hand" v-bind:key="card.name+card.number" :card="card" <Card v-for="card in hand" v-bind:key="card.name+card.number" :card="card"
@click.native="play_card(card, false)" @click.native="play_card(card, false)"
@ -117,7 +117,9 @@ export default {
this.max_lives = self.max_lives this.max_lives = self.max_lives
this.has_played_bang = self.has_played_bang this.has_played_bang = self.has_played_bang
this.is_my_turn = self.is_my_turn this.is_my_turn = self.is_my_turn
if (this.is_my_turn) document.title = 'È il tuo turno! | PewPew!' if (this.is_my_turn) document.title = this.$t('your_turn')+' | PewPew!'
else if (this.pending_action == 3) document.title = this.$t('your_response')+' | PewPew!'
else if (this.pending_action == 5) document.title = this.$t('your_choose')+' | PewPew!'
else document.title = 'PewPew!' else document.title = 'PewPew!'
this.expected_response = self.expected_response this.expected_response = self.expected_response
this.available_cards = self.available_cards this.available_cards = self.available_cards
@ -187,7 +189,7 @@ export default {
return x[this.pending_action] return x[this.pending_action]
}, },
canEndTurn() { canEndTurn() {
return (this.pending_action == 2 && this.hand.length <= this.lives) return (this.pending_action == 2 && this.hand.length <= (this.character.name === "Sean Mallory"?10:this.lives))
}, },
respondCards() { respondCards() {
let cc = [{ let cc = [{
@ -328,14 +330,13 @@ export default {
opacity: 0.5; opacity: 0.5;
} }
.hand { .hand {
margin-top: -16pt;
position: relative; position: relative;
display:flex; display:flex;
border: 1px solid #ccc; border: 2px dashed #ccc;
padding: 10pt 40pt 0pt 40pt; padding: 10pt 40pt 0pt 40pt;
overflow:auto; overflow:auto;
border-radius: 4pt; border-radius: 4pt;
min-height: 20pt; min-height: 40pt;
} }
.hand>.card{ .hand>.card{
margin-left: -30pt; margin-left: -30pt;
@ -344,6 +345,14 @@ export default {
margin-right:35pt; margin-right:35pt;
transform: translateY(-15px); transform: translateY(-15px);
} }
#hand_text{
color: #ccc;
position: absolute;
font-size: xxx-large;
font-weight: 300;
bottom: 0;
right: 10pt;
}
.equipment-slot { .equipment-slot {
display:flex; display:flex;
margin: 10pt 0pt; margin: 10pt 0pt;

View File

@ -1,6 +1,8 @@
<template> <template>
<div style="position:absolute;transform:scale(0.4);bottom:52pt;"> <div style="position:absolute;transform:scale(0.4);bottom:52pt;">
<div class="card back" v-for="(n, i) in ncards" :style="`position:absolute; transform:rotate(${(i-ncards/2)*2}deg) translate(${i*15}px,0);`" v-bind:key="n" :alt="i"> <div :class="{card:true, back:true, delay:ismyturn}" v-for="(n, i) in ncards"
:style="`position:absolute; transform:rotate(${(i-ncards/2)*2}deg) translate(${i*15}px,0); animation-delay:${0.1*i}s`"
v-bind:key="n" :alt="i">
<h4 v-if="n==ncards">PewPew!</h4> <h4 v-if="n==ncards">PewPew!</h4>
<div class="emoji" v-if="n==ncards">💥</div> <div class="emoji" v-if="n==ncards">💥</div>
</div> </div>
@ -12,9 +14,25 @@ export default {
name: 'TinyHand', name: 'TinyHand',
props: { props: {
ncards: Number, ncards: Number,
ismyturn: Boolean
}, },
} }
</script> </script>
<style> <style scoped>
.delay {
animation-name: updown;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes updown {
0% {
top: 0;
}
50% {
top:10pt;
}
100% {
top: 0;
}
}
</style> </style>

View File

@ -6,6 +6,7 @@
"no_lobby_available": "No lobbies available", "no_lobby_available": "No lobbies available",
"create_lobby": "Open a lobby:", "create_lobby": "Open a lobby:",
"lobby_name": "Name:", "lobby_name": "Name:",
"leave_room": "Leave lobby",
"warning": "Warning!", "warning": "Warning!",
"connection_error": "Cannot connect to server.", "connection_error": "Cannot connect to server.",
"end_turn": "End Turn!", "end_turn": "End Turn!",
@ -52,7 +53,7 @@
"pick_a_card": "FLIP A CARD", "pick_a_card": "FLIP A CARD",
"to_defend_from": "TO DEFEND YOURSELF FROM", "to_defend_from": "TO DEFEND YOURSELF FROM",
"submit": "Submit", "submit": "Submit",
"copy": "Copy", "copy": "Copy invite",
"chat": { "chat": {
"chat": "Chat", "chat": "Chat",
"joined": "{0} joined the lobby", "joined": "{0} joined the lobby",
@ -82,5 +83,8 @@
}, },
"mods": "Modifiers", "mods": "Modifiers",
"mod_comp": "Competitive mode (disables automatic take damage)", "mod_comp": "Competitive mode (disables automatic take damage)",
"disconnect_bot": "Replace players that disconnect with bots" "disconnect_bot": "Replace players that disconnect with bots",
"your_turn": "Play your turn!",
"your_response": "Respond!",
"your_choose": "Choose a card!"
} }

View File

@ -6,6 +6,7 @@
"no_lobby_available": "Nessuna stanza disponibile", "no_lobby_available": "Nessuna stanza disponibile",
"create_lobby": "Crea una stanza:", "create_lobby": "Crea una stanza:",
"lobby_name": "Nome:", "lobby_name": "Nome:",
"leave_room": "Esci dalla stanza",
"warning": "Attenzione!", "warning": "Attenzione!",
"connection_error": "Connessione al server assente.", "connection_error": "Connessione al server assente.",
"end_turn": "Termina turno!", "end_turn": "Termina turno!",
@ -52,7 +53,7 @@
"pick_a_card": "ESTRAI UNA CARTA", "pick_a_card": "ESTRAI UNA CARTA",
"to_defend_from": "PER DIFENDERTI DA", "to_defend_from": "PER DIFENDERTI DA",
"submit": "Invia", "submit": "Invia",
"copy": "Copia", "copy": "Copia invito",
"chat": { "chat": {
"chat": "Chat", "chat": "Chat",
"joined": "{0} è entrato nella stanza", "joined": "{0} è entrato nella stanza",
@ -82,5 +83,8 @@
}, },
"mods": "Modificatori", "mods": "Modificatori",
"mod_comp": "Modalità competitiva (disattiva il prendi danno automatico)", "mod_comp": "Modalità competitiva (disattiva il prendi danno automatico)",
"disconnect_bot": "Sostituisci i giocatori che si disconnettono con bot" "disconnect_bot": "Sostituisci i giocatori che si disconnettono con bot",
"your_turn": "È il tuo turno!",
"your_response": "Rispondi!",
"your_choose": "Scegli una carta!"
} }

View File

@ -12,7 +12,7 @@ const routes = [
{ {
path: '/', path: '/',
name: 'Home', name: 'Home',
component: () => import(/* webpackChunkName: "game" */ '../components/Menu.vue') component: () => import(/* webpackChunkName: "home" */ '../components/Menu.vue')
}, },
] ]