feat(DevWeb): Le game fonctionne (avec beeaucoup de chance)

This commit is contained in:
Lucàs
2024-04-26 23:35:16 +02:00
parent 93d6677449
commit 72769670cf
11 changed files with 599 additions and 35 deletions
@@ -42,6 +42,9 @@ public class Game implements Serializable {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private BigDecimal id;
@Transient
private int currentRound = 0;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at")
private Date createdAt;
@@ -78,6 +81,9 @@ public class Game implements Serializable {
@Transient
public static final int NB_ROUNDS_MIN = 3;
@Transient
private GameState gameState = GameState.WAITING;
/**
* Constructeur par défaut
*/
@@ -275,6 +281,10 @@ public class Game implements Serializable {
}
public Deck getDeck() {
if (deck == null) {
deck = new Deck(nbColors, nbValuesPerColor);
deck.shuffle();
}
return deck;
}
@@ -339,5 +349,26 @@ public class Game implements Serializable {
* Difficulté possible d'une partie
*/
public enum Difficulty {EASY, HARD}
public enum GameState {WAITING, STARTED, FINISHED}
public GameState getGameState() {
return gameState;
}
public void setGameState(GameState gameState) {
this.gameState = gameState;
}
public int getCurrentRound() {
return currentRound;
}
public boolean nextRound() {
currentRound++;
if (currentRound > nbRounds) gameState = GameState.FINISHED;
return (currentRound > nbRounds);
}
}
@@ -15,9 +15,11 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.websocket.Session;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Objects;
import uppa.project.json.websocket.ClickChoice;
/**
* Représentation d'un joueur
@@ -60,12 +62,31 @@ public class Player implements Serializable {
@Transient
private Deck deck;
@Transient
private Session session = null;
@Transient
private ClickChoice currentClick = null;
/**
* Constructeur par défaut
*/
public Player() {
}
public Player(Game game, User user, Session session) {
this.game = game;
this.user = user;
this.session = session;
this.score = 0;
this.winner = false;
this.clickCount = 0;
this.rightClickCount = 0;
this.rapidClickCount = 0;
this.deck = new Deck(game.getNbColors(), game.getNbValuesPerColor());
this.deck.shuffle();
}
/**
* Constructeur d'un joueur
*
@@ -295,4 +316,16 @@ public class Player implements Serializable {
public BigDecimal getId() {
return id;
}
public Session getSession() {
return session;
}
public ClickChoice getCurrentClick() {
return currentClick;
}
public void setCurrentClick(ClickChoice currentClick) {
this.currentClick = currentClick;
}
}
@@ -0,0 +1,8 @@
package uppa.project.json.websocket;
public enum ClickChoice {
COLOR_VALUE,
COLOR,
VALUE,
NONE
}
@@ -0,0 +1,32 @@
package uppa.project.json.websocket;
import java.util.ArrayList;
import uppa.project.database.pojo.Card;
import uppa.project.database.pojo.Game;
import uppa.project.database.pojo.Player;
public class SimpleGame {
private final int id;
private final ArrayList<SimplePlayer> players;
private final Card currentCard;
public SimpleGame(Game game, ArrayList<Player> playerArrayList) {
this.id = game.getId().intValue();
this.players = new ArrayList<>();
for (Player p : playerArrayList) players.add(new SimplePlayer(p, game.getCurrentRound()));
this.currentCard = game.getDeck().getCards().get(game.getCurrentRound());
}
public int getId() {
return id;
}
public ArrayList<SimplePlayer> getPlayers() {
return players;
}
public Card getCurrentCard() {
return currentCard;
}
}
@@ -0,0 +1,28 @@
package uppa.project.json.websocket;
public class SimpleInvitation {
private final SimpleUser from;
private final SimpleUser to;
private final int game_id;
public SimpleInvitation(SimpleUser from, SimpleUser to, int game_id) {
this.from = from;
this.to = to;
this.game_id = game_id;
}
public SimpleUser getFrom() {
return from;
}
public SimpleUser getTo() {
return to;
}
public int getGameId() {
return game_id;
}
}
@@ -0,0 +1,64 @@
package uppa.project.json.websocket;
import uppa.project.database.pojo.Card;
import uppa.project.database.pojo.Player;
public class SimplePlayer {
private final SimpleUser user;
private final int score;
private final boolean winner;
private final int clickCount;
private final int rightClickCount;
private final int rapidClickCount;
private final Card currentCard;
public SimplePlayer(Player player, int currentRound) {
this.user = new SimpleUser(player.getUser());
this.score = player.getScore();
this.winner = player.isWinner();
this.clickCount = player.getClickCount();
this.rightClickCount = player.getRightClickCount();
this.rapidClickCount = player.getRapidClickCount();
this.currentCard = player.getDeck().getCards().get(currentRound);
}
public SimplePlayer(Player player) {
this.user = new SimpleUser(player.getUser());
this.score = player.getScore();
this.winner = player.isWinner();
this.clickCount = player.getClickCount();
this.rightClickCount = player.getRightClickCount();
this.rapidClickCount = player.getRapidClickCount();
this.currentCard = null;
}
public Card getCurrentCard() {
return currentCard;
}
public SimpleUser getUser() {
return user;
}
public int getScore() {
return score;
}
public boolean isWinner() {
return winner;
}
public int getClickCount() {
return clickCount;
}
public int getRightClickCount() {
return rightClickCount;
}
public int getRapidClickCount() {
return rapidClickCount;
}
}
@@ -0,0 +1,21 @@
package uppa.project.json.websocket;
import uppa.project.database.pojo.User;
public class SimpleUser {
private final int id;
private final String username;
public SimpleUser(User user) {
this.id = user.getId().intValue();
this.username = user.getUsername();
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
}
@@ -0,0 +1,34 @@
package uppa.project.utils;
import java.util.HashMap;
import uppa.project.database.dao.DAO;
import uppa.project.database.dao.DAOException;
import uppa.project.database.dao.jpa.DAO_JPA_Game;
import uppa.project.database.pojo.Game;
public class GameProvider {
private static DAO<Game> gameDAO;
static {
try {
gameDAO = new DAO_JPA_Game();
} catch (DAOException e) {
throw new RuntimeException(e);
}
}
private static HashMap<Integer, Game> games = new HashMap<>();
public static Game getGame(int gameId) throws DAOException {
if (!games.containsKey(gameId)) {
Game game = gameDAO.findById(gameId);
games.put(gameId, game);
}
if (games.get(gameId) == null) {
throw new DAOException("Game not found");
}
return games.get(gameId);
}
}
@@ -16,6 +16,8 @@ import uppa.project.database.dao.DAOException;
import uppa.project.database.dao.jpa.Game_JPA_DAO_Factory;
import uppa.project.database.pojo.User;
import uppa.project.json.websocket.Message;
import uppa.project.json.websocket.SimpleInvitation;
import uppa.project.json.websocket.SimpleUser;
@ServerEndpoint(value = "/ws/users/{user_id}")
public class ConnectedUsersWS {
@@ -99,7 +101,7 @@ public class ConnectedUsersWS {
// Find session of the user who receive
for (Session s : users.keySet()) {
if (users.get(s).getId().intValue() == invitation.to.id) {
if (users.get(s).getId().intValue() == invitation.getTo().getId()) {
try {
s.getBasicRemote().sendText(message.toJson());
} catch (IOException e) {
@@ -110,29 +112,4 @@ public class ConnectedUsersWS {
}
}
}
private static class SimpleInvitation {
public SimpleUser from;
public SimpleUser to;
public int game_id;
public SimpleInvitation(SimpleUser from, SimpleUser to, int game_id) {
this.from = from;
this.to = to;
this.game_id = game_id;
}
}
private static class SimpleUser {
public int id;
public String username;
public SimpleUser(User user) {
this.id = user.getId().intValue();
this.username = user.getUsername();
}
}
}
@@ -1,5 +1,7 @@
package uppa.project.web.websocket;
import com.google.gson.Gson;
import jakarta.persistence.EntityManager;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
@@ -9,24 +11,46 @@ import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.HashMap;
import uppa.project.database.dao.DAO;
import uppa.project.database.dao.DAOException;
import uppa.project.database.dao.EntityManagerProvider;
import uppa.project.database.dao.jpa.DAO_JPA_User;
import uppa.project.database.pojo.Card;
import uppa.project.database.pojo.Game;
import uppa.project.database.pojo.Player;
import uppa.project.database.pojo.User;
import uppa.project.json.websocket.ClickChoice;
import uppa.project.json.websocket.Message;
import uppa.project.json.websocket.SimpleGame;
import uppa.project.json.websocket.SimplePlayer;
import uppa.project.json.websocket.SimpleUser;
import uppa.project.utils.GameProvider;
@ServerEndpoint(value = "/ws/game/{game_id}/{user_id}")
@ServerEndpoint(value = "/ws/game/{game_id}")
public class GameWS {
public static final HashMap<Session, User> users = new HashMap<>();
public static final HashMap<Game, ArrayList<User>> games = new HashMap<>();
Gson gson = new Gson();
private static final HashMap<Game, ArrayList<Player>> games = new HashMap<>();
private Game game;
private Player player;
@OnOpen
public void onOpen(Session session, @PathParam("game_id") String gameId, @PathParam("user_id") String userId) throws DAOException {
public void onOpen(Session session, @PathParam("game_id") String gameId) throws DAOException {
this.game = GameProvider.getGame(Integer.parseInt(gameId));
if (!games.containsKey(game)) games.put(game, new ArrayList<>());
}
@OnClose
public void onClose(Session session, @PathParam("game_id") String gameId, @PathParam("user_id") String userId) {
public void onClose(Session session, @PathParam("game_id") String gameId) throws DAOException {
games.get(game).remove(this.player);
// Broadcast the new player
ArrayList<SimplePlayer> simplePlayerList = new ArrayList<>();
for (Player player : games.get(game)) simplePlayerList.add(new SimplePlayer(player));
broadcast(new Message("updatePlayerList", gson.toJson(simplePlayerList)).toJson());
}
@OnError
@@ -35,7 +59,137 @@ public class GameWS {
}
@OnMessage
public void onMessage(String message, Session session) {
// Do nothing
public void onMessage(String rawMessage, Session session, @PathParam("game_id") String gameId) throws DAOException {
DAO<User> userDAO = new DAO_JPA_User();
Message message = gson.fromJson(rawMessage, Message.class);
if (message.getType().equals("connection")) {
SimpleUser simpleUser = gson.fromJson(message.getData(), SimpleUser.class);
// find the user
User user = userDAO.findById(simpleUser.getId());
this.player = new Player(game, user, session);
games.get(game).add(this.player);
ArrayList<SimplePlayer> simplePlayerList = new ArrayList<>();
for (Player p : games.get(game)) simplePlayerList.add(new SimplePlayer(p));
// Broadcast the new player
broadcast(new Message("updatePlayerList", gson.toJson(simplePlayerList)).toJson());
}
if (message.getType().equals("start")) {
game.setGameState(Game.GameState.STARTED);
broadcast(new Message("start", gson.toJson(new SimpleGame(game, games.get(game)))).toJson());
// TODO Start Timer
}
if (message.getType().equals("click")) {
ClickChoice choice = gson.fromJson(message.getData(), ClickChoice.class);
int playerScore = player.getScore();
Card gameCard = game.getDeck().getCards().get(game.getCurrentRound());
Card playerCard = player.getDeck().getCards().get(game.getCurrentRound());
player.setCurrentClick(choice);
// get the number of players who clicked
int gameClickCount = 0;
for (Player player : games.get(game)) {
if (player.getCurrentClick() != null) gameClickCount++;
}
// Click count
player.incrementClickCount();
// Right click count
if (gameClickCount == 1) player.incrementRapidClickCount();
// Check if the player has clicked on the right card
switch (choice) {
case COLOR_VALUE -> {
if (gameCard.getColor().equals(playerCard.getColor()) && gameCard.getValue() == playerCard.getValue()) {
player.incrementRightClickCount();
player.setScore(playerScore + 2);
} else {
player.setScore(playerScore - 1);
}
}
case COLOR -> {
if (gameCard.getColor().equals(playerCard.getColor())) {
if (gameCard.getValue() != playerCard.getValue()) {
player.incrementRightClickCount();
player.setScore(playerScore + 2);
} else {
player.setScore(playerScore + 1);
}
} else {
player.setScore(playerScore - 1);
}
}
case VALUE -> {
if(gameCard.getValue() == playerCard.getValue()) {
if (!gameCard.getColor().equals(playerCard.getColor())) {
player.incrementRightClickCount();
player.setScore(playerScore + 2);
} else {
player.setScore(playerScore + 1);
}
} else {
player.setScore(playerScore - 1);
}
}
case NONE -> {
if (!gameCard.getColor().equals(playerCard.getColor()) && gameCard.getValue() != playerCard.getValue()) {
player.incrementRightClickCount();
player.setScore(playerScore + 2);
} else {
player.setScore(playerScore - 1);
}
}
}
// Broadcast the player choice with score
broadcast(new Message("updatePlayer", gson.toJson(new SimplePlayer(player))).toJson());
System.out.println(gameClickCount + " / " + games.get(game).size());
// If all players have clicked
if (gameClickCount >= games.get(game).size()) {
// Reset the current click for all players
for (Player p : games.get(game)) p.setCurrentClick(null);
// Check if the game is over
if (game.nextRound()) { // TODO: if score is the same add a round
broadcast(new Message("nextRound", gson.toJson(new SimpleGame(game, games.get(game)))).toJson());
// TODO Start Timer
} else {
// TODO: determine the winner
// Broadcast the end of the game
broadcast(new Message("end", gson.toJson(new SimpleGame(game, games.get(game)))).toJson());
// TODO: persist the game in the database
// EntityManager em = EntityManagerProvider.getInstance();
// game.setPlayers(games.get(game));
//
// em.getTransaction().begin();
// em.persist(game);
// em.getTransaction().commit();
}
}
}
}
private void broadcast(String message) {
for (Player player : games.get(game)) {
try {
player.getSession().getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@@ -5,12 +5,22 @@
<layout:base>
<component:hero>
<div class="columns">
<div class="columns" id="gameWaiting">
<div class="column">
<component:card title="Liste des joueurs dans la partie">
<jsp:attribute name="footer">
<a data-target="#user-list-modal" class="card-footer-item modal-trigger">Ajouter</a>
<a id="start-game-button" class="is-primary card-footer-item">Démarrer</a>
</jsp:attribute>
<jsp:body>
<table id="playerList" class="table is-fullwidth">
<thead>
<tr><td>Joueur</td></tr>
</thead>
<tbody></tbody>
</table>
</jsp:body>
</component:card>
</div>
<div class="column">
@@ -25,6 +35,23 @@
</component:card>
</div>
</div>
<div id="gameStarted" style="display:none;">
<div class="columns" id="otherCards"></div>
<div class="columns">
<div class="column" id="deck"></div>
<div class="column" id="choice">
<button class="button" data-value="COLOR_VALUE">Même couleur et valeur</button>
<button class="button" data-value="VALUE">Même valeur</button>
<button class="button" data-value="COLOR">Même couleur</button>
<button class="button" data-value="NONE">Aucun</button>
</div>
</div>
<div class="columns">
<div class="column" id="myCard">
</div>
</div>
</div>
</component:hero>
<!-- Liste des utilisateurs dans le lobby -->
@@ -154,4 +181,159 @@
ws.onClose(() => console.log("Disconnected from the server"));
</script>
<script type="module" defer>
import WebsocketToolkit from "${pageContext.request.contextPath}/static/js/WebsocketToolkit.js";
const url = new URL(window.location.href);
url.pathname = "${pageContext.request.contextPath}/ws/game/${game.id}";
url.protocol = "ws:";
url.searchParams.delete("id");
const wsgame = new WebsocketToolkit(url);
wsgame.onOpen(() => {
console.log("Connected to the server (GameWS)")
const message = {
type: "connection",
data: JSON.stringify({
id: ${user.id},
username: "${user.username}"
})
}
wsgame.ws.send(JSON.stringify(message))
});
wsgame.onMessage("updatePlayerList", (data) => {
players = data;
updatePlayerList();
});
wsgame.onMessage("start", (game) => {
currentGame = game;
document.querySelector('#gameWaiting').style.display = 'none';
document.querySelector('#gameStarted').style.display = 'block';
const deck = document.querySelector('#deck');
const choice = document.querySelector('#choice');
const myCard = document.querySelector('#myCard');
const otherCards = document.querySelector('#otherCards');
// Choices
choice.querySelectorAll('button').forEach(button => {
button.addEventListener('click', () => {
const message = {
type: "click",
data: button.dataset.value
}
wsgame.ws.send(JSON.stringify(message));
// Disable buttons
choice.querySelectorAll('button').forEach(button => button.disabled = true);
});
});
// Show other player cards
game.players
.filter(p => p.user.id !== ${user.id})
.forEach(p => {
// Create column in OtherCards
const column = document.createElement('div');
column.classList.add('column');
column.id = 'otherCards-' + p.id;
const cardValue = document.createElement('p');
cardValue.textContent = p.currentCard.color + " " + p.currentCard.value;
column.appendChild(cardValue);
otherCards.appendChild(column);
});
// Show my card
const myCardValue = document.createElement('p');
const me = game.players.find(p => p.user.id === ${user.id});
myCardValue.textContent = me.currentCard.color + " " + me.currentCard.value;
myCard.appendChild(myCardValue);
// Show deck
const deckValue = document.createElement('p');
deckValue.textContent = game.currentCard.color + " " + game.currentCard.value;
deck.appendChild(deckValue);
})
wsgame.onMessage("updatePlayer", (player) => {
})
wsgame.onMessage("end", (game) => {
currentGame = game;
document.querySelector('#gameWaiting').style.display = 'block';
document.querySelector('#gameStarted').style.display = 'none';
})
wsgame.onMessage("nextRound", (game) => {
currentGame = game;
const deck = document.querySelector('#deck');
const choice = document.querySelector('#choice');
const myCard = document.querySelector('#myCard');
const otherCards = document.querySelector('#otherCards');
choice.querySelectorAll('button').forEach(button => button.disabled = false);
// Show other player cards
game.players
.filter(p => p.user.id !== ${user.id})
.forEach(p => {
const column = document.querySelector('#otherCards-' + p.id);
column.innerHTML = '';
const cardValue = document.createElement('p');
cardValue.textContent = p.currentCard.color + " " + p.currentCard.value;
column.appendChild(cardValue);
});
// Show my card
const myCardValue = document.querySelector('#myCard p');
const me = game.players.find(p => p.user.id === ${user.id});
myCardValue.textContent = me.currentCard.color + " " + me.currentCard.value;
// Show deck
const deckValue = document.querySelector('#deck p');
deckValue.textContent = game.currentCard.color + " " + game.currentCard.value;
})
wsgame.onError((error) => console.error(error));
wsgame.onClose(() => {
console.log("Disconnected from the server (GameWS)")
});
// Game
let currentGame;
// Player List
let players = [];
const playerList = document.querySelector('#playerList tbody');
function updatePlayerList() {
playerList.innerHTML = '';
players.forEach(player => {
const tr = document.createElement('tr');
const td = document.createElement('td');
td.textContent = player.user.username;
tr.appendChild(td);
playerList.appendChild(tr);
});
}
// Start Game Button
document.querySelector('#start-game-button').addEventListener('click', () => {
if (players.length < 2 || players.length > 4) {
alert("Il faut entre 2 et 4 joueurs pour démarrer la partie");
return;
}
const message = {type: "start", data: ""}
wsgame.ws.send(JSON.stringify(message));
});
</script>
</layout:base>