GamePool / Game room added

This commit is contained in:
Michael Wain 2024-03-03 02:20:13 +03:00
parent 094bebb7cb
commit 873f099fbc
17 changed files with 321 additions and 95 deletions

View File

@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
@Configuration
@EnableWebSocket
@ -19,6 +20,7 @@ public class WebSocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketHandler(gamePool), "/websocket")
.setAllowedOriginPatterns("*")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS();
}
}

View File

@ -1,5 +1,6 @@
package com.alterdekim.game.component;
import com.alterdekim.game.component.game.GamePool;
import com.alterdekim.game.component.processors.ChatProcessor;
import com.alterdekim.game.component.processors.FriendProcessor;
import com.alterdekim.game.component.processors.Processor;
@ -52,6 +53,9 @@ public class LongPoll {
@Autowired
private FriendServiceImpl friendService;
@Autowired
private GamePool gamePool;
public LongPoll() {
processors.addAll(Arrays.asList(new ChatProcessor(this), new FriendProcessor(this), new RoomProcessor(this)));
}
@ -94,6 +98,7 @@ public class LongPoll {
}
private LongPollResult process(Long userId, LongPollConfig config) {
if( gamePool.containsPlayer(userId) ) config.getGameRedirect().add(new GameRedirect(gamePool.getGameIdByPlayerId(userId).get()));
List<LongPollResultSingle> result = new ArrayList<>();
result.add(new LongPollResultSingle<>(LongPollResultType.OnlineUsers, Arrays.asList(map.size())));
result.add(new LongPollResultSingle<>(LongPollResultType.Redirect, config.getGameRedirect()));

View File

@ -0,0 +1,20 @@
package com.alterdekim.game.component.game;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class BoardGUI {
private List<BoardTile> top;
private List<BoardTile> right;
private List<BoardTile> bottom;
private List<BoardTile> left;
private List<CornerTile> corners;
}

View File

@ -0,0 +1,20 @@
package com.alterdekim.game.component.game;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class BoardTile {
private String uid;
private Integer id;
private Integer cost;
private String stars;
private String img;
private String color;
private String ownerColor;
}

View File

@ -0,0 +1,14 @@
package com.alterdekim.game.component.game;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CornerTile {
private String img;
}

View File

@ -0,0 +1,16 @@
package com.alterdekim.game.component.game;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class GamePlayer {
private Long userId;
private String displayName;
private Integer money;
}

View File

@ -17,17 +17,16 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class GamePool {
@Autowired
private LongPoll longPoll;
@Autowired
private UserServiceImpl userService;
@ -47,8 +46,7 @@ public class GamePool {
List<RoomPlayer> players = roomPlayerService.findByRoomId(r.getId());
roomPlayerService.removeByRoomId(r.getId());
roomService.removeRoom(r.getId());
games.put(r.getId(), new GameRoom(players));
longPoll.notifyPlayers(r.getId(), players);
games.put(r.getId(), new GameRoom(players, userService));
});
}
@ -58,13 +56,38 @@ public class GamePool {
BasicMessage pm = new ObjectMapper().readValue(message, BasicMessage.class);
User u = userService.findById(pm.getUid());
if (u == null || !games.containsKey(pm.getRoomId())) return;
if (!Hash.sha256((u.getId() + u.getUsername() + u.getPassword() + pm.getRoomId()).getBytes()).equals(pm.getAccessToken()))
if (!Hash.sha256((u.getId() + u.getUsername() + u.getPassword() + pm.getRoomId()).getBytes()).equals(pm.getAccessToken())) {
session.close();
return;
}
games.get(pm.getRoomId()).receiveMessage(pm, session);
} catch (JsonProcessingException | NoSuchAlgorithmException e) {
} catch (NoSuchAlgorithmException | IOException e) {
log.error(e.getMessage(), e);
} catch (StringIndexOutOfBoundsException e) {
//log.error(e.getMessage(), e);
}
}
public Boolean containsPlayer(Long userId) {
return games.keySet()
.stream()
.anyMatch(k -> games.get(k)
.getPlayers()
.stream()
.anyMatch(p -> p.getUserId().longValue() == userId.longValue()
)
);
}
public Optional<Long> getGameIdByPlayerId(Long userId) {
return games.keySet()
.stream()
.filter(k -> games.get(k)
.getPlayers()
.stream()
.anyMatch(p -> p.getUserId().longValue() == userId.longValue()
)
)
.findFirst();
}
}

View File

@ -1,40 +1,108 @@
package com.alterdekim.game.component.game;
import com.alterdekim.game.entities.RoomPlayer;
import com.alterdekim.game.service.UserServiceImpl;
import com.alterdekim.game.websocket.message.BasicMessage;
import com.alterdekim.game.websocket.message.ResponseMessage;
import com.alterdekim.game.websocket.message.WebSocketMessageType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
public class GameRoom {
@Getter
private List<RoomPlayer> players;
private List<GamePlayer> players;
private ConcurrentHashMap<Long, WebSocketSession> socks;
public GameRoom(List<RoomPlayer> players) {
this.players = players;
private UserServiceImpl userService;
public GameRoom(List<RoomPlayer> players, UserServiceImpl userService) {
this.userService = userService;
this.players = players.stream().map(p -> new GamePlayer(p.getUserId(), userService.findById(p.getUserId()).getDisplayName(), 0)).collect(Collectors.toList());
this.socks = new ConcurrentHashMap<>();
}
public void receiveMessage(BasicMessage message, WebSocketSession session) {
if(players.stream().noneMatch(p -> p.getUserId().longValue() == message.getUid().longValue())) return;
socks.put(message.getUid(), session);
log.info("GOT MESSAGE " + message.getUid() + " " + message.getAccessToken() + " " + message.getBody());
this.sendMessage(message.getUid());
log.info("receiveMessage " + message.getType());
parseMessage(message);
}
private void sendMessage(Long userId) {
private void parseMessage(BasicMessage message) {
switch (message.getType()) {
case InfoRequest:
sendAllInfoRequest(message);
break;
}
}
private void sendAllInfoRequest(BasicMessage message) {
try {
ObjectMapper om = new ObjectMapper();
List<BoardTile> top = new ArrayList<>();
List<BoardTile> right = new ArrayList<>();
List<BoardTile> bottom = new ArrayList<>();
List<BoardTile> left = new ArrayList<>();
for( int i = 0; i < 9; i++ ) {
top.add(new BoardTile(UUID.randomUUID().toString(), 2 + i, 1000, "", "/static/images/7up.png", "ffffff", "f5f5f5"));
}
for( int i = 0; i < 9; i++ ) {
right.add(new BoardTile(UUID.randomUUID().toString(), i, 1400, "", "/static/images/fanta.png", "bbbbbb", "f5f5f5"));
}
for( int i = 0; i < 9; i++ ) {
bottom.add(new BoardTile(UUID.randomUUID().toString(), 2 + i, 1800, "", "/static/images/cola.png", "eeeeee", "f5f5f5"));
}
for( int i = 10; i >= 2; i-- ) {
left.add(new BoardTile(UUID.randomUUID().toString(), i, 2200, "", "/static/images/beeline.png", "000000", "f5f5f5"));
}
List<CornerTile> corners = new ArrayList<>();
corners.add(new CornerTile("/static/images/start.png"));
corners.add(new CornerTile("/static/images/injail.png"));
corners.add(new CornerTile("/static/images/parking.png"));
corners.add(new CornerTile("/static/images/gotojail.png"));
BoardGUI boardGUI = new BoardGUI(top, right, bottom, left, corners);
sendMessage(message.getUid(), WebSocketMessageType.PlayersList, om.writeValueAsString(players));
sendMessage(message.getUid(), WebSocketMessageType.BoardGUI, om.writeValueAsString(boardGUI));
left.get(2).setCost(12345);
left.get(2).setImg("/static/images/fanta.png");
left.get(2).setColor("bcbcbc");
left.get(2).setStars("★★★");
left.get(2).setOwnerColor("fffbbb");
sendMessage(message.getUid(), WebSocketMessageType.ChangeBoardTileState, om.writeValueAsString(left.get(2)));
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
}
}
private void sendMessage(Long userId, WebSocketMessageType type, String message) {
try {
if (socks.get(userId).isOpen())
socks.get(userId).sendMessage(new TextMessage("HEY!"));
socks.get(userId).sendMessage(
new TextMessage(
new ObjectMapper().writeValueAsString(
new ResponseMessage(type, message)
)
)
);
} catch (IOException e) {
log.error(e.getMessage(), e);
}

View File

@ -7,6 +7,7 @@ import com.alterdekim.game.util.AuthenticationUtil;
import com.alterdekim.game.util.Hash;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

View File

@ -10,6 +10,7 @@ import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
@Slf4j
@AllArgsConstructor
public class WebSocketHandler extends TextWebSocketHandler {
@ -21,10 +22,12 @@ public class WebSocketHandler extends TextWebSocketHandler {
String receivedMessage = (String) message.getPayload();
gamePool.receiveMessage(receivedMessage, session);
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// Perform actions when a new WebSocket connection is established
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
// Perform actions when a WebSocket connection is closed

View File

@ -14,4 +14,5 @@ public class BasicMessage {
private String accessToken;
private Long uid;
private String body;
}
private WebSocketMessageType type;
}

View File

@ -1,4 +0,0 @@
package com.alterdekim.game.websocket.message;
public interface IWebSocketMessage {
}

View File

@ -0,0 +1,14 @@
package com.alterdekim.game.websocket.message;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
public class ResponseMessage {
private WebSocketMessageType type;
private String body;
}

View File

@ -0,0 +1,8 @@
package com.alterdekim.game.websocket.message;
public enum WebSocketMessageType {
PlayersList,
BoardGUI,
InfoRequest,
ChangeBoardTileState
}

View File

@ -1,6 +1,6 @@
body {
font-size: 14px;
font-family: 'Open Sans',tahoma,arial,sans-serif;
font-family: 'Montserrat', sans-serif;
}
._mbtn {

View File

@ -1,25 +1,109 @@
const player_html = '<div class="player" th:data-pid="${p.id}" onClick="drop(this)"><p class="timeout"></p><p class="nickname" th:text="${p.name}"></p><p class="money" th:text="${p.money}"></p><div class="dropbox" style="display: none"></div> <!-- margin-top: -35px; --></div>';
var stompClient = null;
var socket = null;
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
if (socket !== null) {
socket.close();
}
console.log("Disconnected");
}
function sendMessage(message) {
stompClient.send("/", {}, JSON.stringify({'body': JSON.stringify(message), 'accessToken': $("api-tag").attr("data-access-token"), 'uid': $("api-tag").attr("data-uid"), 'roomId': $("api-tag").attr("data-room-id")}));
function sendMessage(message, type) {
socket.send(JSON.stringify({
'type': type,
'body': JSON.stringify(message),
'accessToken': $("api-tag").attr("data-access-token"),
'uid': $("api-tag").attr("data-uid"),
'roomId': $("api-tag").attr("data-room-id")
}));
}
$(document).ready(function() {
var socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/', function (message) {
//showMessage(JSON.parse(message.body).content);
});
});
});
socket = new SockJS('/websocket');
socket.onopen = function() {
console.log('open');
sendMessage({}, 'InfoRequest');
};
socket.onmessage = function(e) {
console.log('message', e.data);
showMessage(JSON.parse(e.data));
};
socket.onclose = function() {
console.log('close');
window.location.assign("/games");
};
});
function showMessage(message) {
console.log('GOT IT');
switch(message.type) {
case 'PlayersList':
parsePlayersList(JSON.parse(message.body));
break;
case 'BoardGUI':
parseBoardGUI(JSON.parse(message.body));
break;
case 'ChangeBoardTileState':
changeBoardState(JSON.parse(message.body));
break;
}
}
function changeBoardState(body) {
let board_tile = $(".board_field[data-fid='" + body.uid + "']");
$(board_tile).find(".cost").css("background-color", "#"+body.color);
$(board_tile).find(".cost").html(body.cost);
$(board_tile).find(".lcost").css("background-color", "#"+body.color);
$(board_tile).find(".lcost").html(body.cost);
$(board_tile).find("._star").html(body.stars);
$(board_tile).find("img").attr("src", body.img);
$(board_tile).find(".fv").css("background-color", "#"+body.ownerColor);
$(board_tile).find(".fh").css("background-color", "#"+body.ownerColor);
}
function parseBoardGUI(body) {
let t_html = '';
for( let i = 0; i < body.top.length; i++ ) {
let board_tile = body.top[i];
t_html += '<div data-fid="'+board_tile.uid+'" class="board_field" style="grid-column: '+board_tile.id+'"><div class="cost" style="background-color: #'+board_tile.color+'">'+board_tile.cost+'</div><div class="fh" style="background-color: #'+board_tile.ownerColor+'"><div class="iconH"><img src="'+board_tile.img+'" class="vertImg"></div><div class="stars"><span class="_star">'+board_tile.stars+'</span></div></div></div>';
}
$("#top_board_tiles").replaceWith(t_html);
let r_html = '';
for( let i = 0; i < body.right.length; i++ ) {
let board_tile = body.right[i];
r_html += '<div data-fid="'+board_tile.uid+'" class="board_field" style="grid-column: 11"><div class="rcost" style="background-color: #'+board_tile.color+'">'+board_tile.cost+'</div><div class="fv" style="background-color: #'+board_tile.ownerColor+'"><div class="iconV"><img src="'+board_tile.img+'" class="horImg"></div><div class="stars hstars"><span class="_star _hstar">'+board_tile.stars+'</span></div></div></div>';
}
$("#right_board_tiles").replaceWith(r_html);
let b_html = '';
for( let i = 0; i < body.bottom.length; i++ ) {
let board_tile = body.bottom[i];
b_html += '<div data-fid="'+board_tile.uid+'" class="board_field" style="grid-row: 11; grid-column: '+board_tile.id+'"><div class="fh" style="background-color: #'+board_tile.ownerColor+'"><div class="iconH"><img class="vertImg" src="'+board_tile.img+'"></div><div class="stars"><span class="_star _vstar">'+board_tile.stars+'</span></div></div><div class="cost" style="background-color: #'+board_tile.color+'">'+board_tile.cost+'</div></div>';
}
$("#bottom_board_tiles").replaceWith(b_html);
let l_html = '';
for( let i = 0; i < body.left.length; i++ ) {
let board_tile = body.left[i];
l_html += '<div data-fid="'+board_tile.uid+'" class="board_field" style="grid-column: 1; grid-row: '+board_tile.id+'"><div class="lcost" style="background-color: #'+board_tile.color+'">'+board_tile.cost+'</div><div class="fv" style="background-color: #'+board_tile.ownerColor+'"><div class="iconV"><img src="'+board_tile.img+'" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar">'+board_tile.stars+'</span></div></div></div>';
}
$("#left_board_tiles").replaceWith(l_html);
let corners = $(".corner");
for(let i = 0; i < body.corners.length; i++ ) {
$(corners[i]).find("img").attr("src", body.corners[i].img);
}
resizeTable();
}
function parsePlayersList(body) {
let p_html = '';
for( let i = 0; i < body.length; i++ ) {
let player = body[i];
p_html += '<div class="player" data-pid="'+player.userId+'" onClick="drop(this)"><p class="timeout"></p><p class="nickname">'+player.displayName+'</p><p class="money">'+player.money+'</p><div class="dropbox" style="display: none"></div> <!-- margin-top: -35px; --></div>';
}
$(".players").append(p_html);
}

View File

@ -5,9 +5,10 @@
<link rel="stylesheet" href="/static/css/game.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js" integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<title th:text="${title} ? ${title} : 'Nosedive'"></title>
<link rel="shortcut icon" type="image/x-icon" href="/static/images/favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,500,800" rel="stylesheet">
<api-tag th:data-access-token="${auth_obj.accessToken}" th:data-uid="${auth_obj.uid}" th:data-room-id="${roomId}"></api-tag>
<div id="contract_field" style="display: none">
<div style="display: inline-flex;">
@ -215,82 +216,32 @@
<!-- </div> -->
<div class="up">
<div class="board_field corner">
<img src="../static/images/start.png" style="width: 100%; height: 100%;" />
<img src="" style="width: 100%; height: 100%;" />
</div>
</div>
<!--<th:block th:each="ft: ${fields_top}">
<div th:data-fid="${ft.uid}" class="board_field" th:style="@{grid-column: {id}(id=${ft.id})}"> --> <!-- 2 + -->
<!-- <div class="cost" th:text="${ft.cost}"></div>
<div class="fh">
<div class="iconH">
<img th:src="${ft.img}" class="vertImg">
</div>
<div class="stars">
<span class="_star" th:text="${ft.stars}"></span>
</div>
</div>
</div>
</th:block>-->
<div id="top_board_tiles"></div>
<div class="up" style="grid-column: 11">
<div class="board_field corner">
<img src="../static/images/injail.png" style="width: 100%; height: 100%;" />
<img src="" style="width: 100%; height: 100%;" />
</div>
</div>
<!--
<th:block th:each="fr: ${fields_right}">
<div class="board_field" style="grid-column: 11">
<div class="rcost" th:text="${fr.cost}"></div>
<div class="fv">
<div class="iconV">
<img th:src="${fr.img}" class="horImg">
</div>
<div class="stars hstars">
<span class="_star _hstar" th:text="${fr.stars}"></span>
</div>
</div>
</div>
</th:block> -->
<div id="right_board_tiles"></div>
<div class="board_field corner" style="grid-column: 11">
<img src="../static/images/parking.png" style="width:100%; height:100%;" />
<img src="" style="width:100%; height:100%;" />
</div>
<!-- <th:block th:each="fb: ${fields_bottom}">
<div class="board_field" th:style="@{grid-row: 11; grid-column: {id}(id=${fb.id})}"> --> <!-- 2 + --> <!--
<div class="fh">
<div class="iconH">
<img class="vertImg" th:src="${fb.img}">
</div>
<div class="stars">
<span class="_star _vstar" th:text="${fb.stars}"></span>
</div>
</div>
<div class="cost" th:text="${fb.cost}"></div>
</div>
</th:block> -->
<div id="bottom_board_tiles"></div>
<div class="board_field corner" style="grid-column: 1; grid-row: 11">
<img src="../static/images/gotojail.png" style="width: 100%; height: 100%;" />
<img src="" style="width: 100%; height: 100%;" />
</div>
<!--
<th:block th:each="fl: ${fields_left}">
<div class="board_field" th:style="@{grid-column: 1; grid-row: {id}(id=${fl.id})}">--> <!-- start from 10, go to 2 --> <!--
<div class="lcost" th:text="${fl.cost}"></div>
<div class="fv">
<div class="iconV">
<img th:src="${fl.img}" class="horImg">
</div>
<div class="stars hstars _hhstar">
<span class="_star _hstar" th:text="${fl.stars}"></span>
</div>
</div>
</div>
</th:block> -->
<div id="left_board_tiles"></div>
</div>
</div>
</div>