diff --git a/pom.xml b/pom.xml index 387dbbab8dc2b78f6cf7919d8c1c3afbbfae99ee..5292ef3be7574e6eb2c66cf9be1175fba38d5387 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,11 @@ </properties> <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-thymeleaf</artifactId> + </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> diff --git a/src/main/java/sk/tuke/gamestudio/entity/Score.java b/src/main/java/sk/tuke/gamestudio/entity/Score.java index b707a5fc07a296f87b55bdf533c3e57da1a7732c..311070ecd98b4c65d483cf43345c584deae7390a 100644 --- a/src/main/java/sk/tuke/gamestudio/entity/Score.java +++ b/src/main/java/sk/tuke/gamestudio/entity/Score.java @@ -9,7 +9,7 @@ import java.util.Date; import java.util.Objects; @Entity -@NamedQuery(name = "Score.selectToScores", query = "select s from Score s where s.game=:game order by s.points desc") +@NamedQuery(name = "Score.selectTopScores", query = "select s from Score s where s.game=:game order by s.points desc") public class Score implements Comparable<Score>, Serializable { @Id @GeneratedValue diff --git a/src/main/java/sk/tuke/gamestudio/server/controller/MinesController.java b/src/main/java/sk/tuke/gamestudio/server/controller/MinesController.java new file mode 100644 index 0000000000000000000000000000000000000000..d4ff56f04a75f699170bebcecaa6a8ec817d7613 --- /dev/null +++ b/src/main/java/sk/tuke/gamestudio/server/controller/MinesController.java @@ -0,0 +1,129 @@ +package sk.tuke.gamestudio.server.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.WebApplicationContext; +import sk.tuke.gamestudio.entity.Score; +import sk.tuke.gamestudio.game.mines.core.Clue; +import sk.tuke.gamestudio.game.mines.core.Field; +import sk.tuke.gamestudio.game.mines.core.GameState; +import sk.tuke.gamestudio.game.mines.core.Tile; +import sk.tuke.gamestudio.service.ScoreService; + +import java.util.Date; + +@Controller +@Scope(WebApplicationContext.SCOPE_SESSION) +@RequestMapping("/mines") +public class MinesController { + @Autowired + private ScoreService scoreService; + + @Autowired + private UserController userController; + + private Field field; + + private boolean marking; + + @RequestMapping + public String mines(String row, String column, Model model) { + if (field == null) + newGame(); + try { + if (marking) + field.markTile(Integer.parseInt(row), Integer.parseInt(column)); + else { + if (field.getState() == GameState.PLAYING) { + field.openTile(Integer.parseInt(row), Integer.parseInt(column)); + if (userController.isLogged() && field.getState() == GameState.SOLVED) { + scoreService.addScore(new Score( + userController.getLoggedUser(), + field.getScore(), + "mines", + new Date() + )); + } + } + } + } catch (NumberFormatException e) { + //Jaro: Zle poslane nic sa nedeje + e.printStackTrace(); + } + prepareModel(model); + return "mines"; + } + + @RequestMapping("/new") + public String newGame(Model model) { + newGame(); + prepareModel(model); + return "mines"; + } + + @RequestMapping("/mark") + public String changeMark(Model model) { + marking = !marking; + prepareModel(model); + return "mines"; + } + + public boolean isMarking() { + return marking; + } + + public GameState getGameState() { + return field.getState(); + } + + //Tento pristup sice nie je idealny, ale pre zaciatok je najjednoduchsi + public String getHtmlField() { + StringBuilder sb = new StringBuilder(); + sb.append("<table class='field'>\n"); + for (int row = 0; row < field.getRowCount(); row++) { + sb.append("<tr>\n"); + for (int column = 0; column < field.getColumnCount(); column++) { + Tile tile = field.getTile(row, column); + sb.append("<td>\n"); + if (field.equals(this.field)) + sb.append("<a href='" + + String.format("/mines?row=%s&column=%s", row, column) + + "'>\n"); + sb.append("<img src='/images/mines/" + getImageName(tile) + ".png'>"); + if (field.equals(this.field)) + sb.append("</a>\n"); + sb.append("</td>\n"); + } + sb.append("</tr>\n"); + } + sb.append("</table>\n"); + + return sb.toString(); + } + + private String getImageName(Tile tile) { + switch (tile.getState()) { + case CLOSED: + return "closed"; + case MARKED: + return "marked"; + case OPEN: + if (tile instanceof Clue) + return "open" + ((Clue) tile).getValue(); + else + return "mine"; + } + throw new IllegalArgumentException("State is not supported " + tile.getState()); + } + + private void prepareModel(Model model) { + model.addAttribute("scores", scoreService.getTopScores("mines")); + } + + private void newGame() { + field = new Field(9, 9, 2); + } +} diff --git a/src/main/java/sk/tuke/gamestudio/server/controller/UserController.java b/src/main/java/sk/tuke/gamestudio/server/controller/UserController.java new file mode 100644 index 0000000000000000000000000000000000000000..9d07bb022b6afc18387f40fed88aff4099fa09d8 --- /dev/null +++ b/src/main/java/sk/tuke/gamestudio/server/controller/UserController.java @@ -0,0 +1,39 @@ +package sk.tuke.gamestudio.server.controller; + +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.WebApplicationContext; + +@Controller +@Scope(WebApplicationContext.SCOPE_SESSION) +@RequestMapping("/user") +public class UserController { + private String loggedUser; + + @RequestMapping("/") + public String index() { + return "index"; + } + + @RequestMapping("/login") + public String login(String login, Model model) { + loggedUser = login; + return "redirect:/"; + } + + @RequestMapping("/logout") + public String logout(Model model) { + loggedUser = null; + return "redirect:/"; + } + + public String getLoggedUser() { + return loggedUser; + } + + public boolean isLogged() { + return loggedUser != null; + } +} diff --git a/src/main/java/sk/tuke/gamestudio/service/ScoreServiceJPA.java b/src/main/java/sk/tuke/gamestudio/service/ScoreServiceJPA.java index 016c985947ab373391073715c091ad17bc5c52a1..b60f3e83e5add3a1b4f6c585b1d1f7688c7ed35b 100644 --- a/src/main/java/sk/tuke/gamestudio/service/ScoreServiceJPA.java +++ b/src/main/java/sk/tuke/gamestudio/service/ScoreServiceJPA.java @@ -19,7 +19,7 @@ public class ScoreServiceJPA implements ScoreService { @Override public List<Score> getTopScores(String gameName) { - return entityManager.createNamedQuery("Score.selectToScores") + return entityManager.createNamedQuery("Score.selectTopScores") .setParameter("game", gameName) .setMaxResults(10).getResultList(); } diff --git a/src/main/resources/static/css/stylesheet.css b/src/main/resources/static/css/stylesheet.css new file mode 100644 index 0000000000000000000000000000000000000000..5462bfd59a33f73f4cc49015a9b94893afe2d092 --- /dev/null +++ b/src/main/resources/static/css/stylesheet.css @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved. + * + * You may not modify, use, reproduce, or distribute this software except in + * compliance with the terms of the License at: + * http://java.net/projects/javaeetutorial/pages/BerkeleyLicense + */ +#top { + position: relative; + background-color: #036fab; + color: white; + padding: 5px; + margin: 0px 0px 10px 0px; +} +body { + background-color: #ffffff; + font-size: 12px; + font-family: Verdana, "Verdana CE", Arial, "Arial CE", "Helvetica CE", sans-serif; + color: #000000; + margin: 10px; +} + +h1 { + font-family: Arial, "Arial CE", "Helvetica CE", sans-serif; + font-size: 28px; + font-weight: bold; + margin: 0px; + padding: 0px; + color: #045491; +} + +h2 { + font-family: Arial, "Arial CE", "Helvetica CE", sans-serif; + font-size: 16px; + font-weight: bold; + margin: 0px; + padding: 0px; + color: #045491; +} + +a:link, a:visited { + color: #045491; + font-weight : bold; + text-decoration: none; +} + +a:link:hover, a:visited:hover { + color: #045491; + font-weight : bold; + text-decoration : underline; +} + +.center_content { + position: relative; + background-color: #e0e0e0; + padding: 5px; +} + +.error-message { + color: #d20005; + font-family: 'New Century Schoolbook', serif; + font-style: oblique; + text-decoration: overline; +} + +.list-background { + background-color: #ffffff; + font-family: sans-serif; + font-size: 12pt; + padding: 10px; + width: 100%; +} + +.list-caption { + font-family: sans-serif; + font-size: 12pt; + font-weight: bold; + padding: 5px; + text-align: center; +} + +.list-column-center { + text-align: center; + width: 15%; +} + +.list-column-left { + text-align: left; + width: 70%; +} + +.list-column-right { + text-align: right; + width: 15%; +} + +.list-footer { + background-color: #A5A5A5; + color: #000000; + font-weight: bold; + text-align: center; +} + +.list-header { + background-color: #ffffff; + color: #000000; + text-align: center; +} + +.list-row-even { +} + +.list-row-odd { + background-color: #eaf1f8; +} + +.special-offer { + font-family: sans-serif; + font-size: 12pt; + text-align: left; + padding: 10px; +} + +table { + border-collapse: collapse; + border: none; +} + +td { + padding: 0; +} \ No newline at end of file diff --git a/src/main/resources/static/images/mines/closed.png b/src/main/resources/static/images/mines/closed.png new file mode 100644 index 0000000000000000000000000000000000000000..7f233bfdccf0b1d65488a0feef1a0e64d7392eb5 Binary files /dev/null and b/src/main/resources/static/images/mines/closed.png differ diff --git a/src/main/resources/static/images/mines/marked.png b/src/main/resources/static/images/mines/marked.png new file mode 100644 index 0000000000000000000000000000000000000000..ffcb92b69c266ec94c4bb47a14926a3318aea51b Binary files /dev/null and b/src/main/resources/static/images/mines/marked.png differ diff --git a/src/main/resources/static/images/mines/mine.png b/src/main/resources/static/images/mines/mine.png new file mode 100644 index 0000000000000000000000000000000000000000..b82db4720dc70a20b6b145d748bf830e87c10263 Binary files /dev/null and b/src/main/resources/static/images/mines/mine.png differ diff --git a/src/main/resources/static/images/mines/open0.png b/src/main/resources/static/images/mines/open0.png new file mode 100644 index 0000000000000000000000000000000000000000..7c648fc494886f27d833b13558e4dc49e952f332 Binary files /dev/null and b/src/main/resources/static/images/mines/open0.png differ diff --git a/src/main/resources/static/images/mines/open1.png b/src/main/resources/static/images/mines/open1.png new file mode 100644 index 0000000000000000000000000000000000000000..80cfc457a2cb0a87a13f232c6709801d4fac86ee Binary files /dev/null and b/src/main/resources/static/images/mines/open1.png differ diff --git a/src/main/resources/static/images/mines/open2.png b/src/main/resources/static/images/mines/open2.png new file mode 100644 index 0000000000000000000000000000000000000000..42d5290ac750f35ee1dbd9e92d0de38f391a0399 Binary files /dev/null and b/src/main/resources/static/images/mines/open2.png differ diff --git a/src/main/resources/static/images/mines/open3.png b/src/main/resources/static/images/mines/open3.png new file mode 100644 index 0000000000000000000000000000000000000000..c8c3441a3df905c4afdb99b2ea7a9fad04daac79 Binary files /dev/null and b/src/main/resources/static/images/mines/open3.png differ diff --git a/src/main/resources/static/images/mines/open4.png b/src/main/resources/static/images/mines/open4.png new file mode 100644 index 0000000000000000000000000000000000000000..a2c6b9e0c4109bad740436f4a8d4ee80ec58e54b Binary files /dev/null and b/src/main/resources/static/images/mines/open4.png differ diff --git a/src/main/resources/static/images/mines/open5.png b/src/main/resources/static/images/mines/open5.png new file mode 100644 index 0000000000000000000000000000000000000000..e136bf9d8716e91c0b61296cfa7fac0df94092a6 Binary files /dev/null and b/src/main/resources/static/images/mines/open5.png differ diff --git a/src/main/resources/static/images/mines/open6.png b/src/main/resources/static/images/mines/open6.png new file mode 100644 index 0000000000000000000000000000000000000000..67616304aaed076be9c77c296c11252a5fdee839 Binary files /dev/null and b/src/main/resources/static/images/mines/open6.png differ diff --git a/src/main/resources/static/images/mines/open7.png b/src/main/resources/static/images/mines/open7.png new file mode 100644 index 0000000000000000000000000000000000000000..d13368a9d487b8266a291d541b9af44820dcb3cc Binary files /dev/null and b/src/main/resources/static/images/mines/open7.png differ diff --git a/src/main/resources/static/images/mines/open8.png b/src/main/resources/static/images/mines/open8.png new file mode 100644 index 0000000000000000000000000000000000000000..ab1729dba483a192eff1ba808cd56fa2801786a9 Binary files /dev/null and b/src/main/resources/static/images/mines/open8.png differ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..397ae8c2d6a1882306927dcda2971ec0666919ff --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html xmlns:th="http://www.thymeleaf.org"> +<head> + <title>Gamestudio</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" href="/css/stylesheet.css"/> +</head> +<body> +<h1>Welcome to Gamestudio!</h1> + +<div th:if="${@userController.logged}"> + You are logged as <span th:text="${@userController.loggedUser}"/>. + <a href="/user/logout">Logout</a> +</div> + +<div th:if="${not @userController.logged}"> + <form method="get" action="/user/login"> + Login: <input name="login" type="text"/> + <input type="submit" value="Login"> + </form> +</div> + +<div> + Our favorite games: + <ul> + <li><a href="/mines">Minesweeper</a></li> + </ul> +</div> +</body> +</html> diff --git a/src/main/resources/templates/mines.html b/src/main/resources/templates/mines.html new file mode 100644 index 0000000000000000000000000000000000000000..d45941f68b678e651044b14172f9bc139119dff5 --- /dev/null +++ b/src/main/resources/templates/mines.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html xmlns:th="http://www.thymeleaf.org"> +<head> + <title>Minesweeper</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="stylesheet" href="/css/stylesheet.css"/> +</head> +<body> + +<h1>Welcome to <a href="/">Gamestudio</a>!</h1> +<h1>Minesweeper</h1> + +<a href="/mines/new">New game</a><br/> +<a href="/mines/mark" th:text='${@minesController.marking ? "Mark" : "Open"}'/><br/> + +<div th:text="${@minesController.gameState}"/> +<div th:utext="${@minesController.htmlField}"/> + +<h2>Scores</h2> +<ol> + <li th:each="score : ${scores}"> + <span th:text="${score.player}"/> + <span th:text="${score.points}"/> + </li> +</ol> + +</body> +</html>