Skip to content

Commit e62b63b

Browse files
authored
Merge pull request #59 from im-yuuki/im-yuuki-patch-2
v1.1.0
2 parents 7867aed + bd7a02a commit e62b63b

35 files changed

+516
-168
lines changed
2.23 MB
Loading
2.53 MB
Loading

.github/images/screenshot-game.png

1.04 MB
Loading
64.1 KB
Loading
163 KB
Loading

README.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,68 @@
1-
![Main diagram](https://bakaafk.s-ul.eu/Wd1HtOZi)
1+
# Exterminator3618
2+
3+
[![Release - JAR](https://github.com/im-yuuki/Exterminator3618/actions/workflows/build-jar.yml/badge.svg?event=release)](https://github.com/im-yuuki/Exterminator3618/actions/workflows/build-jar.yml)
4+
[![Testing](https://github.com/im-yuuki/Exterminator3618/actions/workflows/build-jar.yml/badge.svg?event=release)](https://github.com/im-yuuki/Exterminator3618/actions/workflows/build-jar.yml)
5+
6+
*This game is our team project for the Object-Oriented Programming course at the University of Engineering and Technology (UET), Vietnam National University.*
7+
8+
## ✨ Features
9+
- Classical Arkanoid-style gameplay
10+
- Variety of brick types and power-ups
11+
- Multiple levels with increasing difficulty
12+
- Save game progress and continue where you left off
13+
- Online features: user accounts, 1v1 multiplayer mode, statistics, friend system, ...
14+
- Cross-platform support: Windows, macOS, Linux
15+
16+
## 💻 Screenshots
17+
![Screenshot 1](.github/images/screenshot-mainmenu.png)
18+
![Screenshot 2](.github/images/screenshot-game.png)
19+
20+
## 🎮 Build and run
21+
- **Stable:** Download the latest release from the [Releases](https://github.com/im-yuuki/Exterminator3618/releases) page.
22+
- **Nightly:** Download the latest build from the [Actions](https://github.com/im-yuuki/Exterminator3618/actions/workflows/build-jar.yml) page.
23+
- **Build from source:**
24+
1. Setup JDK and Maven on your system.
25+
2. Clone this repository.
26+
3. Build for desktop: The output JAR file will be located in `desktop/target/`.
27+
```bash
28+
mvn -f ./pom.xml clean install
29+
mvn -f ./desktop/pom.xml clean package
30+
```
31+
4. Build for server: The output JAR file will be located in `server/target/`.
32+
```bash
33+
mvn -f ./server/pom.xml clean package spring-boot:repackage
34+
```
35+
36+
> [!NOTE]
37+
> This project requires Java 21 or higher.\
38+
> Make sure you have Java 21+ installed on your system.\
39+
> We recommend using Adoptium Temurin® JDK (https://adoptium.net/temurin/releases)
40+
41+
## 📚 Used frameworks/libraries
42+
- [libGDX](https://libgdx.com/) - A cross-platform Java game development framework.
43+
- [Jackson Databind](https://github.com/FasterXML/jackson) - A library for processing JSON data in Java.
44+
- [Spring Boot](https://spring.io/projects/spring-boot) - A framework for building production-ready applications in Java.
45+
- [Hibernate](https://hibernate.org/) - An object-relational mapping (ORM) tool for Java.
46+
- [MySQL](https://www.mysql.com/) - A relational database management system.
47+
- [Maven](https://maven.apache.org/) - A build automation tool used primarily for Java projects.
48+
49+
## 🏫 Team members
50+
- @maaL6 Tran Duc Lam (24020197) - Team leader
51+
- @im-yuuki Le Dang Ngo Dan (24020054)
52+
- @BakaAfk Nguyen Xuan Bac (24020036)
53+
- @ngocmai3438 Pham Ngoc Mai (24020216)
54+
55+
*📀 We keep track of the progress in this [GitHub Project](https://github.com/users/im-yuuki/projects/2) site.*
56+
57+
## 📝Architecture diagram (commit f0d4f1af5df58d55fbd364bf59d84c60aeb72d3c)
58+
59+
### Client
60+
![Client](.github/images/io.exterminator3618.client.png)
61+
62+
### Server
63+
![Server](.github/images/io.exterminator3618.server.png)
64+
65+
### Server (Spring Beans)
66+
![Server (Spring Beans)](.github/images/spring-io.exterminator3618.server.png)
67+
68+
*2025 © Exterminator3618 Team. Licensed under the [GNU AGPL-3.0 License](./LICENSE).*

client/pom.xml

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.exterminator3618</groupId>
99
<artifactId>common</artifactId>
10-
<version>1.0.1</version>
10+
<version>1.1.0</version>
1111
<relativePath>../pom.xml</relativePath>
1212
</parent>
1313

@@ -32,10 +32,10 @@
3232
<groupId>ch.qos.logback</groupId>
3333
<artifactId>logback-classic</artifactId>
3434
</dependency>
35-
<dependency>
36-
<groupId>org.projectlombok</groupId>
37-
<artifactId>lombok</artifactId>
38-
</dependency>
35+
<!--<dependency>-->
36+
<!-- <groupId>org.projectlombok</groupId>-->
37+
<!-- <artifactId>lombok</artifactId>-->
38+
<!--</dependency>-->
3939

4040
<!-- https://mvnrepository.com/artifact/com.badlogicgames.gdx/gdx -->
4141
<dependency>
@@ -62,17 +62,11 @@
6262
<version>1.7.4</version>
6363
</dependency>
6464

65-
<!-- https://mvnrepository.com/artifact/org.jetbrains/annotations -->
66-
<dependency>
67-
<groupId>org.jetbrains</groupId>
68-
<artifactId>annotations</artifactId>
69-
<version>26.0.2</version>
70-
</dependency>
65+
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
7166
<dependency>
7267
<groupId>com.fasterxml.jackson.core</groupId>
7368
<artifactId>jackson-databind</artifactId>
74-
<version>2.19.2</version>
75-
<scope>compile</scope>
69+
<version>2.20.1</version>
7670
</dependency>
7771
</dependencies>
7872

client/src/main/java/io/exterminator3618/client/Exterminator3618.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
package io.exterminator3618.client;
22

33
import com.badlogic.gdx.*;
4-
import io.exterminator3618.client.managers.SoundManager;
4+
import io.exterminator3618.client.utils.SoundManager;
55
import io.exterminator3618.client.screens.SplashScreen;
66
import io.exterminator3618.client.utils.Assets;
77
import io.exterminator3618.client.utils.Renderer;
8-
import org.jetbrains.annotations.NotNull;
98
import org.slf4j.Logger;
109
import org.slf4j.LoggerFactory;
1110

1211
import java.util.Stack;
1312

1413
public class Exterminator3618 extends Game {
1514

16-
public static final String GAME_NAME = "Exterminator3618";
17-
public static final String GAME_VERSION = "0.1.0-dev";
18-
1915
private static final Logger log = LoggerFactory.getLogger(Exterminator3618.class);
2016

2117
private final Stack<Screen> screenStack = new Stack<>();
@@ -25,7 +21,6 @@ public class Exterminator3618 extends Game {
2521

2622
@Override
2723
public void create() {
28-
log.info("Starting {} v{}", GAME_NAME, GAME_VERSION);
2924
Assets.load();
3025
launchScreen(new SplashScreen(this));
3126
}
@@ -48,7 +43,6 @@ public void dispose() {
4843
super.dispose();
4944
}
5045

51-
@NotNull
5246
public Preferences getPreferences() {
5347
if (preferences == null) {
5448
log.info("Loading preferences");
@@ -57,7 +51,6 @@ public Preferences getPreferences() {
5751
return preferences;
5852
}
5953

60-
@NotNull
6154
public Renderer getRenderer() {
6255
if (renderer == null) {
6356
log.info("Creating renderer");
@@ -66,7 +59,6 @@ public Renderer getRenderer() {
6659
return renderer;
6760
}
6861

69-
@NotNull
7062
public SoundManager getSoundManager() {
7163
if (soundManager == null) {
7264
log.info("Creating sound manager");
@@ -76,7 +68,7 @@ public SoundManager getSoundManager() {
7668
return soundManager;
7769
}
7870

79-
public void launchScreen(@NotNull Screen screen) {
71+
public void launchScreen(Screen screen) {
8072
log.info("Launching screen: {}", screen.getClass().getSimpleName());
8173
screenStack.push(screen);
8274
super.setScreen(screen);
@@ -113,7 +105,7 @@ public void backToPreviousScreen () {
113105
}
114106
}
115107

116-
public void replaceCurrentScreen(@NotNull Screen screen) {
108+
public void replaceCurrentScreen(Screen screen) {
117109
super.getScreen().dispose();
118110
screenStack.pop();
119111
screenStack.push(screen);
@@ -129,7 +121,7 @@ public Screen getScreen() {
129121

130122
@Override
131123
@Deprecated
132-
public void setScreen(@NotNull Screen screen) {
124+
public void setScreen(Screen screen) {
133125
throw new UnsupportedOperationException("Calling this method is prohibited.");
134126
}
135127

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,94 @@
11
package io.exterminator3618.client.api;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import com.fasterxml.jackson.databind.DeserializationFeature;
5-
import com.fasterxml.jackson.databind.json.JsonMapper;
63
import io.exterminator3618.client.Exterminator3618;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
76

87
import java.io.IOException;
98
import java.net.*;
109
import java.net.http.HttpClient;
1110
import java.net.http.HttpRequest;
11+
import java.net.http.HttpResponse;
12+
import java.time.Duration;
13+
import java.util.ArrayList;
14+
import java.util.concurrent.atomic.AtomicReference;
1215

16+
public class ApiClient implements FriendsApi, MatchApi, UserApi {
1317

14-
public class ApiClient implements FriendsApi, UserApi {
18+
private static final Logger logger = LoggerFactory.getLogger(ApiClient.class);
1519

1620
protected final Exterminator3618 game;
17-
protected final UserInfo userInfo = new UserInfo();
1821
protected final HttpClient httpClient;
19-
protected final JsonMapper jsonMapper = JsonMapper.builder()
20-
.disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
21-
.build();
2222

23-
private ApiClient(Exterminator3618 game, HttpClient httpClient) throws IOException {
23+
protected final UserInfo userInfo = new UserInfo();
24+
protected final ArrayList<UserInfo> friendsList = new ArrayList<>();
25+
26+
private ApiClient(Exterminator3618 game, HttpClient httpClient) {
2427
this.game = game;
2528
this.httpClient = httpClient;
2629
}
2730

2831
public static ApiClient create(Exterminator3618 game) throws IOException {
2932
CookieManager cookieManager = new CookieManager();
30-
HttpClient httpClient = HttpClient.newBuilder()
31-
.cookieHandler(cookieManager)
32-
.build();
33+
HttpClient httpClient = createHttpClient(cookieManager);
3334
ApiClient client = new ApiClient(game, httpClient);
3435
HttpCookie authCookie = new HttpCookie("auth", game.getPreferences().getString("auth"));
3536
authCookie.setDomain(client.getBaseServerUrl());
3637
authCookie.setPath("/");
3738
authCookie.setHttpOnly(true);
3839
cookieManager.getCookieStore().add(URI.create(client.getBaseServerUrl()), authCookie);
40+
if (!client.fetchUserInfo()) {
41+
throw new IOException("Failed to fetch user info");
42+
} else {
43+
logger.info("Authenticated as {} successfully", client.getUserInfo().getUsername());
44+
}
3945
return client;
4046
}
4147

42-
public static ApiClient login(Exterminator3618 game, String username, String password) throws IOException {
43-
HttpClient httpClient = HttpClient.newHttpClient();
48+
public static ApiClient login(Exterminator3618 game, String loginUsername, String loginPassword) throws IOException, InterruptedException {
49+
HttpClient httpClient = createHttpClient(null);
4450
ApiClient client = new ApiClient(game, httpClient);
45-
HttpRequest req = client.createJsonPostRequest("/api/user/login", new Object() {
46-
public final String usernameField = username;
47-
public final String passwordField = password;
51+
HttpRequest req = client.createJsonPostRequest("/account/login", new Object() {
52+
public final String username = loginUsername;
53+
public final String password = loginPassword;
4854
});
55+
HttpResponse<OperationResponse> res = httpClient.send(req, DataProcessor.getOperationResponseHandler());
56+
if (res.statusCode() != 200) {
57+
throw new IOException("Login failed with status code: " + res.statusCode());
58+
} else if (!res.body().isSuccess()) {
59+
throw new IOException("Login failed: " + res.body().getMessage());
60+
}
61+
logger.info("Logged in as {} successfully", loginUsername);
62+
if (!client.fetchUserInfo()) {
63+
throw new IOException("Failed to fetch user info after login");
64+
}
4965
return client;
5066
}
5167

52-
public static ApiClient register(Exterminator3618 game, String name, String username, String password) throws IOException {
53-
HttpClient httpClient = HttpClient.newHttpClient();
68+
public static ApiClient register(Exterminator3618 game, String registerName, String registerUsername, String registerPassword) throws IOException, InterruptedException {
69+
HttpClient httpClient = createHttpClient(null);
5470
ApiClient client = new ApiClient(game, httpClient);
71+
HttpRequest req = client.createJsonPostRequest("/account/register", new Object() {
72+
public final String name = registerName;
73+
public final String username = registerUsername;
74+
public final String password = registerPassword;
75+
});
76+
HttpResponse<OperationResponse> res = httpClient.send(req, DataProcessor.getOperationResponseHandler());
77+
if (res.statusCode() != 200) {
78+
throw new IOException("Registration failed with status code: " + res.statusCode());
79+
} else if (!res.body().isSuccess()) {
80+
throw new IOException("Registration failed: " + res.body().getMessage());
81+
}
82+
logger.info("Registered account {} successfully", registerUsername);
83+
if (!client.fetchUserInfo()) {
84+
throw new IOException("Failed to fetch user info after registration");
85+
}
5586
return client;
5687
}
5788

5889
@Override
5990
public String getBaseServerUrl() {
91+
// return "http://localhost:36018/api"; // mock server URL
6092
return game.getPreferences().getString("server_url", "http://localhost:36018/api");
6193
}
6294

@@ -66,16 +98,52 @@ public HttpClient getHttpClient() {
6698
}
6799

68100
@Override
69-
public String jsonSerializeObject(Object o) {
70-
try {
71-
return jsonMapper.writeValueAsString(o);
72-
} catch (JsonProcessingException e) {
73-
throw new RuntimeException(e);
101+
public UserInfo getUserInfo() {
102+
return userInfo;
103+
}
104+
105+
@Override
106+
public Logger getLogger() {
107+
return logger;
108+
}
109+
110+
public String exportAuthToken() {
111+
AtomicReference<String> authToken = new AtomicReference<>();
112+
getHttpClient().cookieHandler().ifPresent(cookieHandler -> {
113+
if (cookieHandler instanceof CookieManager cookieManager) {
114+
for (HttpCookie cookie : cookieManager.getCookieStore().getCookies()) {
115+
if (cookie.getName().equals("auth")) {
116+
authToken.set(cookie.getValue());
117+
break;
118+
}
119+
}
120+
}
121+
});
122+
return authToken.get();
123+
}
124+
125+
public void saveAuthToken() {
126+
String authToken = exportAuthToken();
127+
if (authToken != null) {
128+
logger.info("Saving auth token to preferences");
129+
game.getPreferences().putString("auth", authToken);
130+
} else {
131+
logger.warn("No auth token found, removing from preferences");
132+
game.getPreferences().remove("auth");
74133
}
75134
}
76135

77-
public UserInfo internalUserInfoObject() {
78-
return userInfo;
136+
private static HttpClient createHttpClient(CookieManager cookieManager) {
137+
CookieManager useCookieManager = cookieManager;
138+
if (useCookieManager == null) {
139+
useCookieManager = new CookieManager();
140+
}
141+
useCookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
142+
return HttpClient.newBuilder()
143+
.cookieHandler(useCookieManager)
144+
.connectTimeout(Duration.ofSeconds(6))
145+
.followRedirects(HttpClient.Redirect.NORMAL)
146+
.build();
79147
}
80148

81149
}

0 commit comments

Comments
 (0)