Skip to content

Commit

Permalink
Speech bubble + min/max played stats (#3)
Browse files Browse the repository at this point in the history
* feat: add speech bubble + stats min/max values
  • Loading branch information
Oliboy50 authored Jun 16, 2022
1 parent cc50689 commit f806ccf
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 104 deletions.
30 changes: 15 additions & 15 deletions stats.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,33 +50,33 @@
$stats_type = [
// Statistics global to table
'table' => [
'playCardsAction' => [
'id' => 10,
'name' => totranslate('Played one or more cards'),
'minValue' => [
'id' => 13,
'name' => totranslate('Minimum value played'),
'type' => 'int',
],
'passTurnAction' => [
'id' => 11,
'name' => totranslate('Turns passed'),
'maxValue' => [
'id' => 14,
'name' => totranslate('Maximum value played'),
'type' => 'int',
],
],

// Statistics existing for each player
'player' => [
'playCardsAction' => [
'id' => 10,
'name' => totranslate('Played one or more cards'),
'numberOfRoundsWon' => [
'id' => 12,
'name' => totranslate('Rounds won'),
'type' => 'int',
],
'passTurnAction' => [
'id' => 11,
'name' => totranslate('Turns passed'),
'minValue' => [
'id' => 13,
'name' => totranslate('Minimum value played'),
'type' => 'int',
],
'numberOfRoundsWon' => [
'id' => 12,
'name' => totranslate('Rounds won'),
'maxValue' => [
'id' => 14,
'name' => totranslate('Maximum value played'),
'type' => 'int',
],
],
Expand Down
92 changes: 84 additions & 8 deletions velonimo.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Board
position: absolute;
top: 0;
left: 2px;
text-align: center;
border: 1px solid black;
border-radius: 8px;
}
Expand All @@ -79,7 +80,7 @@ Board
#board-carpet {
position: relative;
width: 740px;
height: 450px;
height: 480px;
background-color: #35714a;
box-shadow: 2px 2px 5px black;
border: 1px solid #3b3119;
Expand All @@ -99,6 +100,7 @@ Board
box-sizing: border-box;
background-color: rgba(255,255,255,0.7);
padding: 5px;
z-index: 2;
}
.player-table.active {
border: 2px solid #ff0000;
Expand Down Expand Up @@ -141,11 +143,49 @@ Board
width: 1em;
pointer-events: none;
}
.player-table-cards {
position: absolute; /* position is dynamically computed */
width: 130px; /* player table width */
height: 126px; /* card height */
.player-table-speech-bubble {
position: absolute;
top: 0;
width: 50px;
height: 40px;
border-radius: 5px;
background-color: #ffffff;
font-size: 2.2em;
text-align: center;
font-weight: bold;
opacity: 0;
pointer-events: none;
transition-property: opacity;
transition-duration: 0.4s;
}
.player-table-speech-bubble::after {
content: "";
position: absolute;
top: 30px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 20px solid #ffffff;
}
.player-table-speech-bubble.show-bubble {
opacity: 1;
}
.speech-bubble-on-left {
left: -70px;
box-shadow: -2px 2px 5px 0 black;
}
.speech-bubble-on-left::after {
right: -10px;
transform: rotate(-60deg);
}
.speech-bubble-on-right {
right: -70px;
box-shadow: 2px 2px 5px 0 black;
}
.speech-bubble-on-right::after {
left: -10px;
transform: rotate(60deg);
}
.player-table.is-wearing-jersey .player-table-jersey {
position: absolute;
Expand Down Expand Up @@ -175,7 +215,7 @@ Board
background: black;
transform: rotate(45deg);
}
.player-table.is-wearing-jersey.has-used-jersey .jersey-overlay:after {
.player-table.is-wearing-jersey.has-used-jersey .jersey-overlay::after {
content: "";
position: absolute;
left: -20px;
Expand All @@ -189,7 +229,37 @@ END Board
*/

/**
Current player
Board cards
*/
#played-cards {
position: relative;
top: 166px; /* (containerHeight / 2) - (selfHeight / 2) */
left: 235px; /* (containerWidth / 2) - (selfWidth / 2) */
display: flex;
align-items: center;
justify-content: center;
width: 270px; /* cardWidth + (6 * (cardWidth / 3)) */
height: 148px; /* cardHeight + (cardHeight / 6) */
z-index: 4;
}
#last-played-cards {
position: absolute;
bottom: 0;
height: 126px; /* cardHeight */
z-index: 10;
}
#previous-last-played-cards {
position: absolute;
top: 0;
height: 126px; /* cardHeight */
z-index: 8;
}
/**
END Board cards
*/

/**
Current player hand
*/
.spectatorMode #my-hand-wrapper {
display: none;
Expand Down Expand Up @@ -227,7 +297,7 @@ Current player
border: 1px solid gray !important;
}
/**
END Current player
END Current player hand
*/

/**
Expand All @@ -252,6 +322,12 @@ Cards
display: flex;
flex-direction: row;
flex-wrap: nowrap;
opacity: 1;
transition-property: opacity;
transition-duration: 0.5s;
}
.cards-stack.previous-last-played-cards {
opacity: 0.2;
}
.cards-stack .velonimo-card {
margin-right: -60px; /* 2/3 of card width */
Expand Down
80 changes: 65 additions & 15 deletions velonimo.game.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Velonimo extends Table
private const CARD_LOCATION_PLAYER_HAND = 'hand';
private const CARD_LOCATION_DISCARD = 'discard';
private const CARD_LOCATION_PLAYED = 'played';
private const CARD_LOCATION_PREVIOUS_PLAYED = 'previousPlayed';

/** @var Deck (BGA framework component to manage cards) */
private $deck;
Expand Down Expand Up @@ -128,11 +129,11 @@ protected function setupNewGame($players, $options = []) {
// Init game statistics
// (note: statistics used in this file must be defined in your stats.inc.php file)
// Table statistics
self::initStat('table', 'playCardsAction', 0);
self::initStat('table', 'passTurnAction', 0);
self::initStat('table', 'minValue', 0);
self::initStat('table', 'maxValue', 0);
// Player statistics (init for all players)
self::initStat('player', 'playCardsAction', 0);
self::initStat('player', 'passTurnAction', 0);
self::initStat('player', 'minValue', 0);
self::initStat('player', 'maxValue', 0);
self::initStat('player', 'numberOfRoundsWon', 0);

// Create cards
Expand Down Expand Up @@ -213,6 +214,9 @@ protected function getAllDatas() {
);
$result['playedCardsValue'] = (int) self::getGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_VALUE);
$result['playedCardsPlayerId'] = (int) self::getGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_PLAYER_ID);
$result['previousPlayedCards'] = $this->formatCardsForClient(
$this->fromBgaCardsToVelonimoCards($this->deck->getCardsInLocation(self::CARD_LOCATION_PREVIOUS_PLAYED))
);

return $result;
}
Expand All @@ -229,7 +233,7 @@ protected function getAllDatas() {
*/
function getGameProgression() {
$howManyRounds = (int) self::getGameStateValue(self::GAME_OPTION_HOW_MANY_ROUNDS);
$currentRound = (int) self::getGameStateValue(self::GAME_STATE_CURRENT_ROUND);
$currentRound = ((int) self::getGameStateValue(self::GAME_STATE_CURRENT_ROUND)) ?: 1;

return ((int) floor(($currentRound - 1) / $howManyRounds)) * 100;
}
Expand Down Expand Up @@ -323,15 +327,14 @@ function playCards(array $playedCardIds, bool $cardsPlayedWithJersey) {
}

// discard table cards and play cards
$this->deck->moveAllCardsInLocation(self::CARD_LOCATION_PLAYED, self::CARD_LOCATION_DISCARD);
$this->deck->moveAllCardsInLocation(self::CARD_LOCATION_PREVIOUS_PLAYED, self::CARD_LOCATION_DISCARD);
$this->deck->moveAllCardsInLocation(self::CARD_LOCATION_PLAYED, self::CARD_LOCATION_PREVIOUS_PLAYED);
$this->deck->moveCards($playedCardIds, self::CARD_LOCATION_PLAYED, $currentPlayerId);
if ($cardsPlayedWithJersey) {
self::setGameStateValue(self::GAME_STATE_JERSEY_HAS_BEEN_USED_IN_THE_CURRENT_ROUND, 1);
}
self::setGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_PLAYER_ID, $currentPlayerId);
self::setGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_VALUE, $playedCardsValue);
self::incStat(1, 'playCardsAction');
self::incStat(1, 'playCardsAction', $currentPlayerId);
self::notifyAllPlayers('cardsPlayed', clienttranslate('${playerName} plays ${playedCardsValue}'), [
'playedCardsPlayerId' => $currentPlayerId,
'playedCards' => $this->formatCardsForClient($playedCards),
Expand All @@ -340,6 +343,18 @@ function playCards(array $playedCardIds, bool $cardsPlayedWithJersey) {
'withJersey' => $cardsPlayedWithJersey,
]);

// update player's min/max value played
$previousMinValuePlayedByPlayer = (int) $this->getStat('minValue', $currentPlayerId);
if (
$previousMinValuePlayedByPlayer === 0
|| $playedCardsValue < $previousMinValuePlayedByPlayer
) {
$this->setStat($playedCardsValue, 'minValue', $currentPlayerId);
}
if ($playedCardsValue > (int) $this->getStat('maxValue', $currentPlayerId)) {
$this->setStat($playedCardsValue, 'maxValue', $currentPlayerId);
}

// if the player played his last card, set its rank for this round
if ((count($currentPlayerCards) - $numberOfPlayedCards) === 0) {
$currentRound = (int) self::getGameStateValue(self::GAME_STATE_CURRENT_ROUND);
Expand Down Expand Up @@ -387,9 +402,6 @@ function playCards(array $playedCardIds, bool $cardsPlayedWithJersey) {
function passTurn() {
self::checkAction('passTurn');

$this->incStat(1, 'passTurnAction');
$this->incStat(1, 'passTurnAction', (int) self::getCurrentPlayerId());

self::notifyAllPlayers('turnPassed', clienttranslate('${playerName} passes'), [
'playerName' => self::getCurrentPlayerName(),
]);
Expand Down Expand Up @@ -753,9 +765,14 @@ function stEndRound() {
$numberOfPlayers = count($players);
$currentRound = (int) self::getGameStateValue(self::GAME_STATE_CURRENT_ROUND);
$numberOfPointsForRoundByPlayerId = [];
$winnerOfCurrentRound = null;
foreach ($players as $k => $player) {
$playerCurrentRoundRank = $player->getLastRoundRank();
if ($playerCurrentRoundRank === 1) {
$winnerOfCurrentRound = $player;
}
$numberOfPointsForRoundByPlayerId[$player->getId()] = $this->getNumberOfPointsAtRankForRound(
$player->getLastRoundRank(),
$playerCurrentRoundRank,
$currentRound,
$numberOfPlayers
);
Expand All @@ -780,11 +797,23 @@ function stEndRound() {
// re-allow the jersey to be used
self::setGameStateValue(self::GAME_STATE_JERSEY_HAS_BEEN_USED_IN_THE_CURRENT_ROUND, 0);

self::notifyAllPlayers('roundEnded', 'Round #${currentRound} ends', [
self::notifyAllPlayers('roundEnded', 'Round #${currentRound} has been won by ${playerName}', [
'currentRound' => $currentRound,
'playerName' => $winnerOfCurrentRound ? $winnerOfCurrentRound->getName() : 'N/A',
'players' => $this->formatPlayersForClient($players),
]);

// notify points earned by each player
foreach ($players as $player) {
$translatedMessage = ($numberOfPointsForRoundByPlayerId[$player->getId()] > 0)
? clienttranslate('${playerName} does not get any point')
: clienttranslate('${playerName} wins ${points} points');
self::notifyAllPlayers('pointsWon', $translatedMessage, [
'playerName' => $player->getName(),
'points' => $numberOfPointsForRoundByPlayerId[$player->getId()],
]);
}

$howManyRounds = (int) self::getGameStateValue(self::GAME_OPTION_HOW_MANY_ROUNDS);
$isGameOver = $currentRound >= $howManyRounds;

Expand Down Expand Up @@ -831,6 +860,24 @@ function stEndRound() {
'closing' => $isGameOver ? clienttranslate('End of game') : clienttranslate('Next round')
));

// update global min/max value played stats
if ($isGameOver) {
$minValuePlayedGlobally = 1000;
$maxValuePlayedGlobally = 0;
foreach ($players as $player) {
$minValuePlayedByPlayer = (int) $this->getStat('minValue', $player->getId());
if ($minValuePlayedByPlayer < $minValuePlayedGlobally) {
$minValuePlayedGlobally = $minValuePlayedByPlayer;
}
$maxValuePlayedByPlayer = (int) $this->getStat('maxValue', $player->getId());
if ($maxValuePlayedByPlayer > $maxValuePlayedGlobally) {
$maxValuePlayedGlobally = $maxValuePlayedByPlayer;
}
}
$this->setStat($minValuePlayedGlobally, 'minValue');
$this->setStat($maxValuePlayedGlobally, 'maxValue');
}

// go to next round or end the game
$this->gamestate->nextState($isGameOver ? 'endGame' : 'nextRound');
}
Expand Down Expand Up @@ -973,7 +1020,9 @@ private function getCardsValue(array $cards, bool $withJersey): int {
* @return VelonimoPlayer[]
*/
private function getPlayersFromDatabase(): array {
$players = array_values(self::getCollectionFromDB('SELECT player_id, player_no, player_name, player_color, player_score, rounds_ranking, is_wearing_jersey FROM player'));
$players = array_values(self::getCollectionFromDB(
'SELECT player_id, player_no, player_name, player_color, player_score, rounds_ranking, is_wearing_jersey FROM player'
));

return array_map(
fn (array $player) => new VelonimoPlayer(
Expand All @@ -983,7 +1032,7 @@ private function getPlayersFromDatabase(): array {
$player['player_color'],
(int) $player['player_score'],
VelonimoPlayer::deserializeRoundsRanking($player['rounds_ranking']),
((int) $player['is_wearing_jersey']) === 1,
((int) $player['is_wearing_jersey']) === 1
),
$players
);
Expand Down Expand Up @@ -1148,6 +1197,7 @@ private function getCurrentLoser(array $players): ?VelonimoPlayer {
}

private function discardLastPlayedCards(): void {
$this->deck->moveAllCardsInLocation(self::CARD_LOCATION_PREVIOUS_PLAYED, self::CARD_LOCATION_DISCARD);
$this->deck->moveAllCardsInLocation(self::CARD_LOCATION_PLAYED, self::CARD_LOCATION_DISCARD);
self::setGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_VALUE, 0);
self::setGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_PLAYER_ID, 0);
Expand Down
Loading

0 comments on commit f806ccf

Please sign in to comment.