Skip to content

My Projects

The projects I'm focused on right now.

Screenshot coming soon
In development

ArcClash

A turn-based, anime-inspired card-drafting duel game I'm building backend-first with Java 21 and Spring Boot 4. The rules engine and a first REST API with PostgreSQL persistence are implemented and tested; the playable game and its client are still to come.

JavaSpring BootREST APIsSpring Data JPAHibernatePostgreSQLFlywayMavenJUnit 5Mockito
Architecture, stack & roadmap
16
JUnit tests
2
REST endpoints
6
Engine packages

Built so far

  • Rules engine in plain Java: character stats, a three-stance counter cycle, Duel Power calculation and duel resolution
  • Hidden five-character draft, magic cards with their effects, and battlefield events
  • Set and match flow for the Quick Duel and Championship modes
  • REST API to create a match and fetch it by id, persisted in PostgreSQL
  • Request validation (400) and not-found handling (404), covered by tests

Architecture

A modular monolith in one Spring Boot application. The match API follows Controller → Service → Repository with DTOs at the edges; the game rules live in a separate plain-Java engine with no Spring, HTTP or database code.

  1. Controller · MatchController (@RestController) maps the routes and validates request bodies with @Valid
  2. DTOs · Java records: CreateMatchRequest (mode is @NotNull) and MatchResponse, so entities never leave the service layer
  3. Service · MatchService creates matches with UUID ids, maps entities to responses and raises 404 for unknown ids
  4. Repository · MatchRepository extends JpaRepository<MatchEntity, UUID>
  5. Database · MatchEntity on a PostgreSQL table created by a Flyway migration: UUID key, mode stored as text with a CHECK constraint, timestamp set by the database
  6. Game engine · Draft, duels, stances, magic, events and match coordination in plain Java. Not yet connected to the API

REST API

  • POST/api/matches

    Create a match. Body: { "mode": "QUICK_DUEL" | "CHAMPIONSHIP" }

    201 Created with the new match id · 400 if mode is missing

  • GET/api/matches/{id}

    Fetch a match: mode, set wins for each player, finished flag, created time

    200 OK · 404 if no match has that id

Technical decisions

Rules engine without framework code
The engine has no Spring or JPA imports, so the game rules are unit-tested quickly in isolation and can be reused by the API.
Schema owned by Flyway
Hibernate runs with ddl-auto=validate: it checks the entities against the migrated schema but never changes it.
Open Session in View disabled
spring.jpa.open-in-view=false keeps database access inside the service layer instead of during view rendering.
Immutable DTOs and constructor injection
Requests and responses are Java records validated with Bean Validation; dependencies are injected through constructors.
Errors via ResponseStatusException
A missing match maps to 404 with a clear reason. A global @ControllerAdvice for uniform error bodies isn't built yet.

Tech stack

Backend
Java 21Spring Boot 4Spring Web MVCBean Validation
Data
Spring Data JPAHibernatePostgreSQLFlyway
Build & tests
MavenJUnit 5MockitoMockMvc

Testing

16 JUnit 5 tests: a unit test for each engine component (stances, stats, Duel Power, duel resolution, draft, magic, events, sets and match flow), a context-load test, and four @WebMvcTest slice tests that use MockMvc with a Mockito-mocked service to check 201, 200, 400 and 404 responses.

Planned (not built yet)

  • Connect the rules engine to the API, with match state persisted in PostgreSQL
  • Clear, non-leaking error responses and duplicate-safe commands (from the design doc)
  • Computer opponent for single-player matches
  • React + TypeScript game client
  • Spring Security once player accounts arrive
  • Docker packaging and Testcontainers integration tests
  • Real-time PvP over WebSockets
Screenshot coming soon
Not live yet

AnimeGuess

An AI-powered anime character guessing game. Players ask yes/no questions about a hidden character and an AI answers from that character's stored profile, then they try to name them. It has several game modes, accounts, leaderboards and ranked real-time multiplayer: a Next.js frontend on a Spring Boot, PostgreSQL and Redis backend. It isn't live yet.

JavaSpring BootSpring SecuritySpring AIREST APIsWebSocketsSpring Data JPAHibernatePostgreSQLFlywayRedisNext.jsReactTypeScriptTailwind CSSDockerGitHub ActionsJUnit 5MockitoTestcontainersMavenVercelRender

Source code is private.

Architecture, stack & roadmap
21
REST controllers
92
Flyway migrations
28
JPA entities
84
Tests

What's built

  • Ask yes/no questions about a hidden character; an AI answers from that character's stored profile, then you guess who it is
  • Modes: Classic, Daily Challenge, Single Anime, Quick Game, Progressive Hints and Quiz (multiple choice with AI-written clues at three difficulty levels)
  • Typo-tolerant guess matching: "lufy" or just "luffy" both count for Monkey D. Luffy
  • Accounts with email verification, password reset, and Google or GitHub sign-in
  • Ranked real-time multiplayer with matchmaking and an Elo-style rating
  • Leaderboards, login streaks, achievements, friends and messages, plus a shop and avatar customisation

Architecture

A Next.js frontend talks to a layered Spring Boot API over REST and WebSockets. The backend is organised into controller, service, repository, entity, DTO, security, filter and exception packages.

  1. Frontend · Next.js 14 and React 18 with TypeScript and Tailwind CSS; TanStack Query for API data, React Hook Form + Zod for forms, STOMP over SockJS for live games
  2. API · 21 @RestController classes over 37 services, documented with OpenAPI; a global exception handler returns one consistent error format
  3. Security · Stateless Spring Security: JWT authentication filter, BCrypt password hashing, OAuth2 login with Google and GitHub, reCAPTCHA on registration and guest games
  4. Real time · STOMP WebSocket endpoint with a SockJS fallback; the JWT is checked during the WebSocket handshake
  5. AI · Spring AI ChatModel calls OpenAI with a prompt template built from the character's profile
  6. Data · PostgreSQL through Spring Data JPA (28 entities, 92 Flyway migrations); Redis for rate limiting, live game state and leaderboard caching

Technical decisions

Ground the AI in stored data
Each answer is generated from the hidden character's own profile and restricted to "yes", "no" or "I'm not sure", so answers stay consistent and on-topic.
Validate before calling the AI
Questions are checked to be yes/no questions first, so open-ended questions never cost an AI call.
Stateless auth everywhere
The same JWT secures REST requests and the WebSocket handshake; there are no server sessions to share between instances.
Schema in migrations
All 92 schema changes are versioned Flyway migrations.

Challenges & solutions

Keeping AI costs and abuse under control
Bucket4j rate limits backed by Redis, so they hold across instances, with separate budgets for the AI question endpoint, sign-in, polling and other routes; reCAPTCHA v3 on registration and guest games; non-yes/no questions rejected before any AI call.
Live multiplayer state
Game conversations live in Redis with an expiry, and players join through JWT-authenticated STOMP connections.
Fair ranked matches
A matchmaking queue with a scheduler, and an Elo-style rating update (K-factor 32) after each game.
Forgiving answers
Fuzzy name matching allows one typo and partial names, with its own unit tests.

Tech stack

Frontend
Next.js 14React 18TypeScriptTailwind CSSTanStack QueryZod
Backend
Java 17Spring Boot 3Spring SecurityJWT & OAuth2WebSockets (STOMP)Spring AI
Data
PostgreSQLSpring Data JPAFlywayRedis
Quality & delivery
JUnit 5MockitoTestcontainersDockerGitHub ActionsSentry

Testing

84 tests: PostgreSQL integration tests on Testcontainers for authentication and the game service, @WebMvcTest controller tests with Mockito, and unit tests for question validation, guess matching, game rules and reCAPTCHA. GitHub Actions runs separate backend and frontend CI.