Skip to content

Commit

Permalink
Merge branch 'main' into feature
Browse files Browse the repository at this point in the history
  • Loading branch information
toshydev committed Aug 14, 2023
2 parents bff5f2f + 19bd0ff commit bcb5420
Show file tree
Hide file tree
Showing 42 changed files with 616 additions and 144 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-snek-test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Deploy App on SNEK"
name: "Deploy Test-App on SNEK"

on:
push:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ NetRunner is a unique real-world exploration game set in a cyberpunk-inspired un

5. **User Authentication:** Players must register and log in to access the game's features. Spring Security ensures secure user authentication and management.

6. **Customizable Protagonist:** Players can personalize their in-game character, choosing the protagonist's gender, appearance, and background to create a unique identity in the cyberpunk universe.
6. **Customizable Protagonist:** Players can personalize their in-game character, choosing the appearance, and theme to create a unique identity in the cyberpunk universe.

**Current State of the Project:**
NetRunner is currently under development, with the frontend designed using React, Vite, and Mapbox to integrate the cyberpunk-themed interface with real-world map exploration based on players' GPS positions. The backend relies on Spring Boot, Spring WebFlux, and MongoDB to handle real-time multiplayer interactions, node data, and user profiles effectively. Comprehensive testing, utilizing JUnit, AssertJ, Mockito, and MockMvc, ensures stable and reliable gameplay.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import click.snekhome.backend.model.Coordinates;
import click.snekhome.backend.model.Player;
import click.snekhome.backend.service.PlayerService;
import click.snekhome.backend.util.ItemSize;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -52,4 +53,9 @@ public List<Player> getEnemies() {
return this.playerService.getEnemies();
}

@ResponseStatus(HttpStatus.ACCEPTED)
@PutMapping("store")
public Player buyItem(@RequestBody String itemSize) {
return this.playerService.buyItem(ItemSize.valueOf(itemSize));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
.authorizeHttpRequests(httpRequests ->
httpRequests
.requestMatchers(HttpMethod.GET, "/*").permitAll()
.requestMatchers(HttpMethod.GET, "/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/user/login").permitAll()
.requestMatchers(HttpMethod.POST, "/api/user/register").permitAll()
.requestMatchers(HttpMethod.POST, "/api/user/logout").permitAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import click.snekhome.backend.repo.PlayerRepo;
import click.snekhome.backend.security.UserData;
import click.snekhome.backend.util.IdService;
import click.snekhome.backend.util.ItemSize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.List;

import static click.snekhome.backend.util.PlayerFunctions.buyAttackPoints;

@Service
public class PlayerService {
private final PlayerRepo playerRepo;
Expand Down Expand Up @@ -101,4 +104,14 @@ public List<Player> getEnemies() {
Player player = this.getPlayer(username);
return this.playerRepo.findAllByNameIsNot(player.name());
}

public Player buyItem(ItemSize itemSize) {
String username = SecurityContextHolder
.getContext()
.getAuthentication()
.getName();
Player player = this.getPlayer(username);
Player updatedPlayer = buyAttackPoints(player, itemSize);
return this.playerRepo.save(updatedPlayer);
}
}
13 changes: 13 additions & 0 deletions backend/src/main/java/click/snekhome/backend/util/ItemSize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package click.snekhome.backend.util;

public enum ItemSize {
SMALL("SMALL"),
MEDIUM("MEDIUM"),
LARGE("LARGE");

final String size;

ItemSize(String itemSize) {
this.size = itemSize;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,49 @@ public static Player useScan(Player player) {
newLastScan
);
}

public static int increaseAttackPoints(int attackPoints, int maxAttackPoints, int increase) {
return Math.min(attackPoints + increase, maxAttackPoints);
}

public static Player buyAttackPoints(Player player, ItemSize itemSize) {
int newCredits = player.credits();
int newAttackPoints = switch (itemSize) {
case SMALL -> {
if (player.credits() >= 1000) {
newCredits = player.credits() - 1000;
yield increaseAttackPoints(player.attack(), player.maxAttack(), 1);
}
yield player.attack();
}
case MEDIUM -> {
if (player.credits() >= 4000) {
newCredits = player.credits() - 4000;
yield increaseAttackPoints(player.attack(), player.maxAttack(), 5);
}
yield player.attack();
}
case LARGE -> {
if (player.credits() >= 7500) {
newCredits = player.credits() - 7500;
yield increaseAttackPoints(player.attack(), player.maxAttack(), 10);
}
yield player.attack();
}
};
return new Player(
player.id(),
player.userId(),
player.name(),
player.coordinates(),
player.level(),
player.experience(),
player.experienceToNextLevel(),
player.health(),
player.maxHealth(),
newAttackPoints,
player.maxAttack(),
newCredits,
player.lastScan()
);}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -1017,6 +1016,92 @@ void expectOneNewNodeWhenScanReturnsOneUnusedLocation() throws Exception {
.andExpect(MockMvcResultMatchers.content().json(scanResponse));
}

@Test
@DirtiesContext
@WithMockUser(username = "playerunknown")
void expectUnchangedPlayerWhenBuyingItemWithNotEnoughCredits() throws Exception {
Player player = new Player("abc", "123", "playerunknown", new Coordinates(0, 0, 0), 1, 0, 100, 100, 100, 5, 15, 0, 0);
this.playerRepo.insert(player);
String expected = """
{
"name": "playerunknown",
"id": "abc",
"userId": "123",
"coordinates": {
"latitude": 0,
"longitude": 0,
"timestamp": 0
},
"level": 1,
"experience": 0,
"health": 100,
"maxHealth": 100,
"credits": 0,
"attack": 5,
"maxAttack": 15,
"lastScan": 0
}
""";

mockMvc.perform(MockMvcRequestBuilders.put("/api/player/store")
.content("SMALL")
.with(csrf()))
.andExpect(MockMvcResultMatchers.status().isAccepted())
.andExpect(MockMvcResultMatchers.content().json(expected));
mockMvc.perform(MockMvcRequestBuilders.put("/api/player/store")
.content("MEDIUM")
.with(csrf()))
.andExpect(MockMvcResultMatchers.status().isAccepted())
.andExpect(MockMvcResultMatchers.content().json(expected));
mockMvc.perform(MockMvcRequestBuilders.put("/api/player/store")
.content("LARGE")
.with(csrf()))
.andExpect(MockMvcResultMatchers.status().isAccepted())
.andExpect(MockMvcResultMatchers.content().json(expected));
}

@Test
@DirtiesContext
@WithMockUser(username = "playerunknown")
void expectMaximumPlayerAttackWhenBuyingTooManyItemsWithEnoughCredits() throws Exception {
Player player = new Player("abc", "123", "playerunknown", new Coordinates(0, 0, 0), 1, 0, 100, 100, 100, 5, 15, 20000, 0);
this.playerRepo.insert(player);
String expected = """
{
"name": "playerunknown",
"id": "abc",
"userId": "123",
"coordinates": {
"latitude": 0,
"longitude": 0,
"timestamp": 0
},
"level": 1,
"experience": 0,
"health": 100,
"maxHealth": 100,
"credits": 7500,
"attack": 15,
"maxAttack": 15,
"lastScan": 0
}
""";

mockMvc.perform(MockMvcRequestBuilders.put("/api/player/store")
.content("SMALL")
.with(csrf()))
.andExpect(MockMvcResultMatchers.status().isAccepted());
mockMvc.perform(MockMvcRequestBuilders.put("/api/player/store")
.content("MEDIUM")
.with(csrf()))
.andExpect(MockMvcResultMatchers.status().isAccepted());
mockMvc.perform(MockMvcRequestBuilders.put("/api/player/store")
.content("LARGE")
.with(csrf()))
.andExpect(MockMvcResultMatchers.status().isAccepted())
.andExpect(MockMvcResultMatchers.content().json(expected));
}

@AfterAll
static void cleanUp() throws IOException {
mockWebServer.close();
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import MapView from "./components/MapView.tsx";
import ViewChangeButton from "./components/ViewChangeButton.tsx";
import RechargingButton from "./components/RechargingButton.tsx";
import NavBar from "./components/NavBar.tsx";
import StorePage from "./components/StorePage.tsx";

export default function App() {
const [initialLoad, setInitialLoad] = useState(true)
Expand Down Expand Up @@ -111,6 +112,12 @@ export default function App() {
<ViewChangeButton view={"map"}/>
</>
}/>
<Route path={"/store"} element={
<>
<StorePage/>
<ViewChangeButton view={"map"}/>
</>
}/>
</Route>
<Route path={"/login"} element={<LoginPage/>}/>
</Routes>
Expand Down
1 change: 0 additions & 1 deletion frontend/src/assets/svg/cctv-camera.svg

This file was deleted.

7 changes: 7 additions & 0 deletions frontend/src/assets/svg/cctv_enemy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit bcb5420

Please sign in to comment.