GamePool / Game room added
This commit is contained in:
parent
ccc87d57f4
commit
094bebb7cb
8
pom.xml
8
pom.xml
@ -44,6 +44,14 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-messaging</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.thymeleaf.extras</groupId>
|
<groupId>org.thymeleaf.extras</groupId>
|
||||||
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
||||||
|
24
src/main/java/com/alterdekim/game/WebSocketConfig.java
Normal file
24
src/main/java/com/alterdekim/game/WebSocketConfig.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package com.alterdekim.game;
|
||||||
|
|
||||||
|
import com.alterdekim.game.component.game.GamePool;
|
||||||
|
import com.alterdekim.game.websocket.WebSocketHandler;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocket
|
||||||
|
@ComponentScan("com.alterdekim.game.component.game")
|
||||||
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private GamePool gamePool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
|
registry.addHandler(new WebSocketHandler(gamePool), "/websocket")
|
||||||
|
.setAllowedOriginPatterns("*")
|
||||||
|
.withSockJS();
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import com.alterdekim.game.component.result.LongPollResult;
|
|||||||
import com.alterdekim.game.component.result.LongPollResultSingle;
|
import com.alterdekim.game.component.result.LongPollResultSingle;
|
||||||
import com.alterdekim.game.component.result.LongPollResultType;
|
import com.alterdekim.game.component.result.LongPollResultType;
|
||||||
import com.alterdekim.game.dto.*;
|
import com.alterdekim.game.dto.*;
|
||||||
|
import com.alterdekim.game.entities.RoomPlayer;
|
||||||
import com.alterdekim.game.service.*;
|
import com.alterdekim.game.service.*;
|
||||||
import com.alterdekim.game.util.Hash;
|
import com.alterdekim.game.util.Hash;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -69,14 +70,20 @@ public class LongPoll {
|
|||||||
});
|
});
|
||||||
getLongPollingQueue().forEach(longPollingSession -> {
|
getLongPollingQueue().forEach(longPollingSession -> {
|
||||||
try {
|
try {
|
||||||
if( !map.containsKey(longPollingSession.getUserId())) map.put(longPollingSession.getUserId(), new LongPollConfig(0L,new ArrayList<>(), 0, Hash.rnd(), new ArrayList<>(), System.currentTimeMillis(), new ArrayList<>()));
|
if( !map.containsKey(longPollingSession.getUserId())) map.put(longPollingSession.getUserId(), new LongPollConfig(0L));
|
||||||
LongPollConfig config = map.get(longPollingSession.getUserId());
|
LongPollConfig config = map.get(longPollingSession.getUserId());
|
||||||
LongPollResult result = process(longPollingSession.getUserId(), config);
|
LongPollResult result = process(longPollingSession.getUserId(), config);
|
||||||
config.setSession_pass(config.getSession_pass()+1);
|
config.setSession_pass(config.getSession_pass()+1);
|
||||||
if( !result.getResultWithType(LongPollResultType.InviteResult, InviteResult.class).isEmpty() || !result.getResultWithType(LongPollResultType.FriendResult, FriendResult.class).isEmpty() || !result.getResultWithType(LongPollResultType.RoomResult, RoomResultV2.class).isEmpty() || !result.getResultWithType(LongPollResultType.ChatResult, ChatResult.class).isEmpty() || config.getSession_pass() >= iterations) {
|
if( !config.getGameRedirect().isEmpty() ||
|
||||||
|
!result.getResultWithType(LongPollResultType.InviteResult, InviteResult.class).isEmpty() ||
|
||||||
|
!result.getResultWithType(LongPollResultType.FriendResult, FriendResult.class).isEmpty() ||
|
||||||
|
!result.getResultWithType(LongPollResultType.RoomResult, RoomResultV2.class).isEmpty() ||
|
||||||
|
!result.getResultWithType(LongPollResultType.ChatResult, ChatResult.class).isEmpty() ||
|
||||||
|
config.getSession_pass() >= iterations) {
|
||||||
longPollingSession.getDeferredResult().setResult(result);
|
longPollingSession.getDeferredResult().setResult(result);
|
||||||
config.setSession_pass(0);
|
config.setSession_pass(0);
|
||||||
config.setInvites(new ArrayList<>());
|
config.setInvites(new ArrayList<>());
|
||||||
|
config.setGameRedirect(new ArrayList<>());
|
||||||
}
|
}
|
||||||
map.put(longPollingSession.getUserId(), config);
|
map.put(longPollingSession.getUserId(), config);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -89,8 +96,19 @@ public class LongPoll {
|
|||||||
private LongPollResult process(Long userId, LongPollConfig config) {
|
private LongPollResult process(Long userId, LongPollConfig config) {
|
||||||
List<LongPollResultSingle> result = new ArrayList<>();
|
List<LongPollResultSingle> result = new ArrayList<>();
|
||||||
result.add(new LongPollResultSingle<>(LongPollResultType.OnlineUsers, Arrays.asList(map.size())));
|
result.add(new LongPollResultSingle<>(LongPollResultType.OnlineUsers, Arrays.asList(map.size())));
|
||||||
|
result.add(new LongPollResultSingle<>(LongPollResultType.Redirect, config.getGameRedirect()));
|
||||||
processors.forEach(p -> result.add(p.process(config, userId)));
|
processors.forEach(p -> result.add(p.process(config, userId)));
|
||||||
result.add(new LongPollResultSingle<>(LongPollResultType.InviteResult, config.getInvites().stream().map(i -> new InviteResult(i.getRoomId(), i.getUserId(), i.getUsername())).collect(Collectors.toList())));
|
result.add(new LongPollResultSingle<>(LongPollResultType.InviteResult, config.getInvites().stream().map(i -> new InviteResult(i.getRoomId(), i.getUserId(), i.getUsername())).collect(Collectors.toList())));
|
||||||
return new LongPollResult(result);
|
return new LongPollResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void notifyPlayers(Long roomId, List<RoomPlayer> players) {
|
||||||
|
players.forEach(p -> {
|
||||||
|
LongPollConfig lc = map.get(p.getUserId());
|
||||||
|
List<GameRedirect> gr = lc.getGameRedirect();
|
||||||
|
gr.add(new GameRedirect(roomId));
|
||||||
|
lc.setGameRedirect(gr);
|
||||||
|
map.put(p.getUserId(), lc);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package com.alterdekim.game.component;
|
package com.alterdekim.game.component;
|
||||||
|
|
||||||
import com.alterdekim.game.dto.GameInvite;
|
import com.alterdekim.game.dto.GameInvite;
|
||||||
|
import com.alterdekim.game.dto.GameRedirect;
|
||||||
import com.alterdekim.game.dto.RoomResult;
|
import com.alterdekim.game.dto.RoomResult;
|
||||||
import com.alterdekim.game.dto.UserResult;
|
import com.alterdekim.game.dto.UserResult;
|
||||||
|
import com.alterdekim.game.util.Hash;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -22,4 +24,16 @@ public class LongPollConfig {
|
|||||||
private List<UserResult> friends_online;
|
private List<UserResult> friends_online;
|
||||||
private Long lastRequest;
|
private Long lastRequest;
|
||||||
private List<GameInvite> invites;
|
private List<GameInvite> invites;
|
||||||
|
private List<GameRedirect> gameRedirect;
|
||||||
|
|
||||||
|
public LongPollConfig(Long last_chat_id) {
|
||||||
|
this.last_chat_id = last_chat_id;
|
||||||
|
this.rooms = new ArrayList<>();
|
||||||
|
this.session_pass = 0;
|
||||||
|
this.poll_token = Hash.rnd();
|
||||||
|
this.friends_online = new ArrayList<>();
|
||||||
|
this.lastRequest = System.currentTimeMillis();
|
||||||
|
this.invites = new ArrayList<>();
|
||||||
|
this.gameRedirect = new ArrayList<>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package com.alterdekim.game.component.game;
|
||||||
|
|
||||||
|
import com.alterdekim.game.component.LongPoll;
|
||||||
|
import com.alterdekim.game.entities.RoomPlayer;
|
||||||
|
import com.alterdekim.game.entities.User;
|
||||||
|
import com.alterdekim.game.service.RoomPlayerServiceImpl;
|
||||||
|
import com.alterdekim.game.service.RoomServiceImpl;
|
||||||
|
import com.alterdekim.game.service.UserServiceImpl;
|
||||||
|
import com.alterdekim.game.util.Hash;
|
||||||
|
import com.alterdekim.game.websocket.message.BasicMessage;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class GamePool {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LongPoll longPoll;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserServiceImpl userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RoomServiceImpl roomService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RoomPlayerServiceImpl roomPlayerService;
|
||||||
|
|
||||||
|
private ConcurrentHashMap<Long, GameRoom> games = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 1000)
|
||||||
|
private void refreshRooms() {
|
||||||
|
roomService.getAllActive()
|
||||||
|
.forEach(r -> {
|
||||||
|
if( roomPlayerService.findByRoomId(r.getId()).size() != r.getPlayerCount().intValue() ) return;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveMessage(String message, WebSocketSession session) {
|
||||||
|
try {
|
||||||
|
message = message.substring(message.indexOf("{"));
|
||||||
|
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()))
|
||||||
|
return;
|
||||||
|
games.get(pm.getRoomId()).receiveMessage(pm, session);
|
||||||
|
} catch (JsonProcessingException | NoSuchAlgorithmException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
} catch (StringIndexOutOfBoundsException e) {
|
||||||
|
//log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.alterdekim.game.component.game;
|
||||||
|
|
||||||
|
import com.alterdekim.game.entities.RoomPlayer;
|
||||||
|
import com.alterdekim.game.websocket.message.BasicMessage;
|
||||||
|
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.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class GameRoom {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private List<RoomPlayer> players;
|
||||||
|
|
||||||
|
private ConcurrentHashMap<Long, WebSocketSession> socks;
|
||||||
|
|
||||||
|
public GameRoom(List<RoomPlayer> players) {
|
||||||
|
this.players = players;
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(Long userId) {
|
||||||
|
try {
|
||||||
|
if (socks.get(userId).isOpen())
|
||||||
|
socks.get(userId).sendMessage(new TextMessage("HEY!"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,5 +5,6 @@ public enum LongPollResultType {
|
|||||||
ChatResult,
|
ChatResult,
|
||||||
RoomResult,
|
RoomResult,
|
||||||
FriendResult,
|
FriendResult,
|
||||||
InviteResult
|
InviteResult,
|
||||||
|
Redirect
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ public class APIController {
|
|||||||
if( longPoll.getMap().containsKey(userId) ){
|
if( longPoll.getMap().containsKey(userId) ){
|
||||||
LongPollConfig c = longPoll.getMap().get(userId);
|
LongPollConfig c = longPoll.getMap().get(userId);
|
||||||
if( !c.getPoll_token().equals(poll_token) ) {
|
if( !c.getPoll_token().equals(poll_token) ) {
|
||||||
c = new LongPollConfig(last_chat_id, rooms, 0, poll_token, friends, System.currentTimeMillis(), new ArrayList<>());
|
c = new LongPollConfig(last_chat_id, rooms, 0, poll_token, friends, System.currentTimeMillis(), new ArrayList<>(), new ArrayList<>());
|
||||||
longPoll.getLongPollingQueue().removeIf(q -> q.getUserId().longValue() == userId.longValue());
|
longPoll.getLongPollingQueue().removeIf(q -> q.getUserId().longValue() == userId.longValue());
|
||||||
}
|
}
|
||||||
c.setRooms(rooms);
|
c.setRooms(rooms);
|
||||||
@ -217,7 +217,7 @@ public class APIController {
|
|||||||
c.setLastRequest(System.currentTimeMillis());
|
c.setLastRequest(System.currentTimeMillis());
|
||||||
longPoll.getMap().put(userId, c);
|
longPoll.getMap().put(userId, c);
|
||||||
} else {
|
} else {
|
||||||
longPoll.getMap().put(userId, new LongPollConfig(last_chat_id, rooms, 0, poll_token, friends, System.currentTimeMillis(), new ArrayList<>()));
|
longPoll.getMap().put(userId, new LongPollConfig(last_chat_id, rooms, 0, poll_token, friends, System.currentTimeMillis(), new ArrayList<>(), new ArrayList<>()));
|
||||||
}
|
}
|
||||||
longPoll.getLongPollingQueue().add(new LongPollingSession(userId, deferredResult));
|
longPoll.getLongPollingQueue().add(new LongPollingSession(userId, deferredResult));
|
||||||
return deferredResult;
|
return deferredResult;
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.alterdekim.game.controller;
|
||||||
|
|
||||||
|
import com.alterdekim.game.dto.AuthApiObject;
|
||||||
|
import com.alterdekim.game.entities.User;
|
||||||
|
import com.alterdekim.game.service.UserServiceImpl;
|
||||||
|
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.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Controller
|
||||||
|
public class GameController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserServiceImpl userService;
|
||||||
|
|
||||||
|
@GetMapping("/game")
|
||||||
|
public String gamePage(Model model, @RequestParam("id") Long roomId) {
|
||||||
|
model.addAttribute("roomId", roomId);
|
||||||
|
try {
|
||||||
|
User u = AuthenticationUtil.authProfile(model, userService);
|
||||||
|
String apiKey = Hash.sha256((u.getId() + u.getUsername() + u.getPassword() + roomId).getBytes());
|
||||||
|
model.addAttribute("auth_obj", new AuthApiObject(apiKey, u.getId(), Hash.rnd()));
|
||||||
|
} catch ( Exception e ) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return "game";
|
||||||
|
}
|
||||||
|
}
|
@ -48,11 +48,6 @@ public class StaticController {
|
|||||||
return "rules";
|
return "rules";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/game")
|
|
||||||
public String gamePage(Model model) {
|
|
||||||
return "game";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/games")
|
@GetMapping("/games")
|
||||||
public String gamesPage(Model model) {
|
public String gamesPage(Model model) {
|
||||||
try {
|
try {
|
||||||
|
16
src/main/java/com/alterdekim/game/dto/GameField.java
Normal file
16
src/main/java/com/alterdekim/game/dto/GameField.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package com.alterdekim.game.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class GameField {
|
||||||
|
private Long uid;
|
||||||
|
private Integer id;
|
||||||
|
private String cost;
|
||||||
|
private String img;
|
||||||
|
private String stars;
|
||||||
|
}
|
10
src/main/java/com/alterdekim/game/dto/GameRedirect.java
Normal file
10
src/main/java/com/alterdekim/game/dto/GameRedirect.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package com.alterdekim.game.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class GameRedirect {
|
||||||
|
private Long roomId;
|
||||||
|
}
|
@ -22,6 +22,11 @@ public interface RoomPlayerRepository extends JpaRepository<RoomPlayer, Long> {
|
|||||||
@Query(value = "DELETE FROM RoomPlayer r WHERE r.userId = :userId")
|
@Query(value = "DELETE FROM RoomPlayer r WHERE r.userId = :userId")
|
||||||
void deleteAllByUserId(@Param("userId") Long userId);
|
void deleteAllByUserId(@Param("userId") Long userId);
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Modifying
|
||||||
|
@Query(value = "DELETE FROM RoomPlayer r WHERE r.roomId = :roomId")
|
||||||
|
void removeByRoomId(@Param("roomId") Long roomId);
|
||||||
|
|
||||||
@Query(value = "SELECT r.roomId FROM RoomPlayer r WHERE r.userId = :userId ORDER BY r.roomId ASC LIMIT 1")
|
@Query(value = "SELECT r.roomId FROM RoomPlayer r WHERE r.userId = :userId ORDER BY r.roomId ASC LIMIT 1")
|
||||||
Long hasUserId(@Param("userId") Long userId);
|
Long hasUserId(@Param("userId") Long userId);
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ public class SpringSecurity {
|
|||||||
.requestMatchers("/friends").hasAnyAuthority("ROLE_ADMIN")
|
.requestMatchers("/friends").hasAnyAuthority("ROLE_ADMIN")
|
||||||
.requestMatchers("/followers").hasAnyAuthority("ROLE_ADMIN")
|
.requestMatchers("/followers").hasAnyAuthority("ROLE_ADMIN")
|
||||||
.requestMatchers("/settings").hasAnyAuthority("ROLE_ADMIN")
|
.requestMatchers("/settings").hasAnyAuthority("ROLE_ADMIN")
|
||||||
|
.requestMatchers("/websocket/**").hasAnyAuthority("ROLE_ADMIN")
|
||||||
.requestMatchers("/static/**").permitAll()
|
.requestMatchers("/static/**").permitAll()
|
||||||
.requestMatchers("/access-denied").permitAll()
|
.requestMatchers("/access-denied").permitAll()
|
||||||
.requestMatchers("/signup").permitAll()
|
.requestMatchers("/signup").permitAll()
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.alterdekim.game.security;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.config.annotation.web.socket.EnableWebSocketSecurity;
|
||||||
|
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocketSecurity
|
||||||
|
public class WebSocketSecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
|
||||||
|
return AuthorityAuthorizationManager.hasAnyAuthority("ROLE_ADMIN");
|
||||||
|
}
|
||||||
|
}
|
@ -36,4 +36,8 @@ public class RoomPlayerServiceImpl implements RoomPlayerService{
|
|||||||
public Long hasUserId(Long userId) {
|
public Long hasUserId(Long userId) {
|
||||||
return repository.hasUserId(userId);
|
return repository.hasUserId(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeByRoomId(Long roomId) {
|
||||||
|
repository.removeByRoomId(roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,8 @@ public class RoomServiceImpl implements RoomService {
|
|||||||
public void clearEmptyRooms() {
|
public void clearEmptyRooms() {
|
||||||
roomRepository.clearEmptyRooms();
|
roomRepository.clearEmptyRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeRoom(Long roomId) {
|
||||||
|
roomRepository.deleteById(roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package com.alterdekim.game.util;
|
package com.alterdekim.game.util;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class Hash {
|
public class Hash {
|
||||||
public static String sha256( byte[] b ) throws Exception {
|
public static String sha256( byte[] b ) throws NoSuchAlgorithmException {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
return bytesToHex(digest.digest(b));
|
return bytesToHex(digest.digest(b));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.alterdekim.game.websocket;
|
||||||
|
|
||||||
|
import com.alterdekim.game.component.game.GamePool;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.WebSocketMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class WebSocketHandler extends TextWebSocketHandler {
|
||||||
|
|
||||||
|
private GamePool gamePool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.alterdekim.game.websocket.message;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BasicMessage {
|
||||||
|
private Long roomId;
|
||||||
|
private String accessToken;
|
||||||
|
private Long uid;
|
||||||
|
private String body;
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package com.alterdekim.game.websocket.message;
|
||||||
|
|
||||||
|
public interface IWebSocketMessage {
|
||||||
|
}
|
25
src/main/resources/static/javascript/game.js
Normal file
25
src/main/resources/static/javascript/game.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
function disconnect() {
|
||||||
|
if (stompClient !== null) {
|
||||||
|
stompClient.disconnect();
|
||||||
|
}
|
||||||
|
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")}));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -61,6 +61,12 @@ function takeInviteMessage(obj) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function takeRedirect(reds) {
|
||||||
|
for( let i = 0; i < reds.length; i++ ) {
|
||||||
|
window.location.assign("/game?id=" + reds[i].roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function successPolling(data) {
|
function successPolling(data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
data = data.result;
|
data = data.result;
|
||||||
@ -89,6 +95,9 @@ function successPolling(data) {
|
|||||||
case "InviteResult":
|
case "InviteResult":
|
||||||
invites = res.array;
|
invites = res.array;
|
||||||
break;
|
break;
|
||||||
|
case "Redirect":
|
||||||
|
takeRedirect(res.array);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<html xmlns:th="http://www.w3.org/1999/xhtml">
|
<html xmlns:th="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"></meta>
|
<meta charset="utf-8"/>
|
||||||
<link rel="stylesheet" href="/static/css/game.css">
|
<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://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
<title>Монополия онлайн</title>
|
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
|
||||||
<link rel="shortcut icon" type="image/png" href="../static/images/favicon.ico"/>
|
<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>
|
||||||
|
<title th:text="${title} ? ${title} : 'Nosedive'"></title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/static/images/favicon.ico">
|
||||||
|
<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 id="contract_field" style="display: none">
|
||||||
<div style="display: inline-flex;">
|
<div style="display: inline-flex;">
|
||||||
<div class="contract_image" style="margin-left: 10px"><img src="../static/images/mitsubishi.png" style="width: 55px;"></div>
|
<div class="contract_image" style="margin-left: 10px"><img src="../static/images/mitsubishi.png" style="width: 55px;"></div>
|
||||||
@ -26,14 +29,12 @@
|
|||||||
<div class="wrapper" style="width: 100%; height: 100%">
|
<div class="wrapper" style="width: 100%; height: 100%">
|
||||||
<div class="game">
|
<div class="game">
|
||||||
<div class="players">
|
<div class="players">
|
||||||
<th:block th:each="p: ${players}">
|
<!--<div class="player" th:data-pid="${p.id}" onClick="drop(this)">
|
||||||
<div class="player" th:data-pid="${p.id}" onClick="drop(this)">
|
<p class="timeout"></p>
|
||||||
<p class="timeout"></p>
|
<p class="nickname" th:text="${p.name}"></p>
|
||||||
<p class="nickname" th:text="${p.name}"></p>
|
<p class="money" th:text="${p.money}"></p>
|
||||||
<p class="money" th:text="${p.money}"></p>
|
<div class="dropbox" style="display: none"></div> margin-top: -35px;
|
||||||
<div class="dropbox" style="display: none"></div> <!-- margin-top: -35px; -->
|
</div>-->
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<div class="table_center">
|
<div class="table_center">
|
||||||
@ -212,61 +213,90 @@
|
|||||||
<div class="chip purple" data-trow=0 data-tcol=0></div>
|
<div class="chip purple" data-trow=0 data-tcol=0></div>
|
||||||
<div class="chip orange" data-trow=0 data-tcol=0></div>
|
<div class="chip orange" data-trow=0 data-tcol=0></div>
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<div class="up"><div class="board_field corner"><img src="../static/images/start.png" style="width: 100%; height: 100%;" /></div></div>
|
<div class="up">
|
||||||
|
<div class="board_field corner">
|
||||||
|
<img src="../static/images/start.png" style="width: 100%; height: 100%;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<th:block th:each="f: ${fields_up}">
|
<!--<th:block th:each="ft: ${fields_top}">
|
||||||
<div th:data-fid="${f.uid}" class="board_field" th:style="grid-column: @{${f.id}}"> <!-- 2 + -->
|
<div th:data-fid="${ft.uid}" class="board_field" th:style="@{grid-column: {id}(id=${ft.id})}"> --> <!-- 2 + -->
|
||||||
<div class="cost" th:text="${f.cost}"></div>
|
<!-- <div class="cost" th:text="${ft.cost}"></div>
|
||||||
<div class="fh">
|
<div class="fh">
|
||||||
<div class="iconH">
|
<div class="iconH">
|
||||||
<img th:src="${f.img}" class="vertImg">
|
<img th:src="${ft.img}" class="vertImg">
|
||||||
</div>
|
</div>
|
||||||
<div class="stars">
|
<div class="stars">
|
||||||
<span class="_star" th:text="${f.stars}"></span>
|
<span class="_star" th:text="${ft.stars}"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>-->
|
||||||
|
|
||||||
<div class="up" style="grid-column: 11"><div class="board_field corner"><img src="../static/images/injail.png" style="width: 100%; height: 100%;" /></div></div>
|
<div class="up" style="grid-column: 11">
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/lacoste.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
<div class="board_field corner">
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/mitsubishi.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
<img src="../static/images/injail.png" style="width: 100%; height: 100%;" />
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/vw.png" class="horImg"></div></div><div class="stars hstars"><span class="_star _hstar"></span></div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/mcdonalds.svg" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/apple.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/vw.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
<!--
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/macdonalds.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
<th:block th:each="fr: ${fields_right}">
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/nike.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
<div class="board_field" style="grid-column: 11">
|
||||||
<div class="board_field" style="grid-column: 11"><div class="rcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/pepsi.png" class="horImg"></div><div class="stars hstars"><span class="_star _hstar"></span></div></div></div>
|
<div class="rcost" th:text="${fr.cost}"></div>
|
||||||
<div class="board_field corner" style="grid-column: 11"><img src="../static/images/parking.png" style="width:100%; height:100%;" /></div>
|
<div class="fv">
|
||||||
<div class="board_field" style="grid-column: 2; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/nike.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
<div class="iconV">
|
||||||
<div class="board_field" style="grid-column: 3; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/mitsubishi.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
<img th:src="${fr.img}" class="horImg">
|
||||||
<div class="board_field" style="grid-column: 4; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/peugeot.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 5; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/vw.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
<div class="stars hstars">
|
||||||
<div class="board_field" style="grid-column: 6; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/linux.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
<span class="_star _hstar" th:text="${fr.stars}"></span>
|
||||||
<div class="board_field" style="grid-column: 7; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/apple.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 8; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/burger_king.svg" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 9; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/lacoste.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 10; grid-row: 11;"><div class="fh"><div class="iconH"><img src="../static/images/pepsi.png" class="vertImg"></div><div class="stars"><span class="_star _vstar"></span></div></div><div class="cost">0</div></div>
|
</th:block> -->
|
||||||
<div class="board_field corner" style="grid-column: 1; grid-row: 11"><img src="../static/images/gotojail.png" style="width: 100%; height: 100%;" /></div>
|
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 10;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/peugeot.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<div class="board_field corner" style="grid-column: 11">
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 9;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/apple.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<img src="../static/images/parking.png" style="width:100%; height:100%;" />
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 8;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/linux.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
</div>
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 7;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/macdonalds.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 6;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/pepsi.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<!-- <th:block th:each="fb: ${fields_bottom}">
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 5;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/burger_king.svg" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<div class="board_field" th:style="@{grid-row: 11; grid-column: {id}(id=${fb.id})}"> --> <!-- 2 + --> <!--
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 4;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/mitsubishi.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<div class="fh">
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 3;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/adidas.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<div class="iconH">
|
||||||
<div class="board_field" style="grid-column: 1; grid-row: 2;"><div class="lcost">0</div><div class="fv"><div class="iconV"><img src="../static/images/lacoste.png" class="horImg"></div><div class="stars hstars _hhstar"><span class="_star _hstar"></span></div></div></div>
|
<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 class="board_field corner" style="grid-column: 1; grid-row: 11">
|
||||||
|
<img src="../static/images/gotojail.png" 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
<script type="text/javascript" src="/games/cookie.js"></script>
|
<script type="text/javascript" src="/static/javascript/game.js"></script>
|
||||||
<script type="text/javascript" src="/games/db.js"></script>
|
|
||||||
<script type="text/javascript" src="/games/methods.js"></script>
|
|
||||||
<script type="text/javascript" src="game.js"></script>
|
|
||||||
<script type="text/javascript" src="/static/javascript/scale.js"></script>
|
<script type="text/javascript" src="/static/javascript/scale.js"></script>
|
||||||
</footer>
|
</footer>
|
||||||
</html>
|
</html>
|
Loading…
x
Reference in New Issue
Block a user