a
This commit is contained in:
parent
82b9cfe780
commit
8dc716b4bf
31 changed files with 527 additions and 27 deletions
44
README.md
44
README.md
|
|
@ -4,8 +4,8 @@ This project contains a TCP Game Server, a Java Swing Client, and a Spring Boot
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- `server/`: Java TCP Server (Port 1870). With Anticheat (Server-side validation).
|
- `server/`: Java TCP Server (Port 1870). Includes Anticheat logic (validates turns, moves, wins).
|
||||||
- `client/`: Standalone Java Swing Client. Connects to `dokploy.lona-development.org:1870`.
|
- `client/`: Standalone Java Swing Client. Defaults to `dokploy.lona-development.org:1870` but allows custom host/port.
|
||||||
- `web-client/`: Spring Boot Web Application. Acts as a proxy to the TCP Server via WebSocket.
|
- `web-client/`: Spring Boot Web Application. Acts as a proxy to the TCP Server via WebSocket.
|
||||||
|
|
||||||
## Running with Docker Compose
|
## Running with Docker Compose
|
||||||
|
|
@ -16,28 +16,44 @@ To start the Server and Web Client:
|
||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Server** runs on port `1870`.
|
- **Server** runs on port `1870` (exposed).
|
||||||
- **Web Client** runs on `http://localhost:8080`.
|
- **Web Client** runs on `http://localhost:8080`.
|
||||||
|
|
||||||
## Building the Java Client
|
## Building and Running the Standalone Java Client
|
||||||
|
|
||||||
To build the standalone Java client:
|
Go to the `client` directory and run the build script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./build_client.sh
|
cd client
|
||||||
|
chmod +x build.sh
|
||||||
|
./build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
To run it:
|
### Usage
|
||||||
|
|
||||||
|
By default, the client attempts to connect to `dokploy.lona-development.org:1870`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java -jar client/target/client-1.0-SNAPSHOT.jar
|
java -jar target/client-1.0-SNAPSHOT.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** The Java client is hardcoded to connect to `dokploy.lona-development.org`. Ensure this domain resolves to your server IP (or adds `127.0.0.1 dokploy.lona-development.org` to `/etc/hosts` for local testing).
|
**Custom Host/Port (Local Testing):**
|
||||||
|
If you want to connect to a local server (e.g., running via Docker Compose), pass the host and port as arguments:
|
||||||
|
|
||||||
## Game Protocol
|
```bash
|
||||||
|
java -jar target/client-1.0-SNAPSHOT.jar localhost 1870
|
||||||
|
```
|
||||||
|
|
||||||
- **CREATE**: Starts a new game. Server returns a 6-character code.
|
## Game Protocol (TCP 1870)
|
||||||
- **JOIN <CODE>**: Joins an existing game.
|
|
||||||
- **MOVE <ROW> <COL>**: Places your symbol.
|
The server creates game sessions identified by a unique 6-character code.
|
||||||
- **SURRENDER**: Forfeits the game.
|
|
||||||
|
- **CREATE**: Starts a new game. Server returns `GAME_CREATED <CODE>`.
|
||||||
|
- **JOIN <CODE>**: Joins an existing game. Server returns `JOIN_SUCCESS` or `ERROR`.
|
||||||
|
- **MOVE <ROW> <COL>**: Places your symbol (0-2). Validated by server.
|
||||||
|
- **SURRENDER**: Forfeits the game immediately.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Server Connection Refused?** Ensure the server container is running and port 1870 is mapped.
|
||||||
|
- **Client stuck connecting?** Verify the hostname is reachable. Use `localhost` if running locally.
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ mvn clean package
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "--------------------------------------------------"
|
echo "--------------------------------------------------"
|
||||||
echo "Build Successful!"
|
echo "Build Successful!"
|
||||||
echo "You can run the client with:"
|
echo "You can run the client with default settings (dokploy.lona-development.org):"
|
||||||
echo "java -jar target/client-1.0-SNAPSHOT.jar"
|
echo "java -jar target/client-1.0-SNAPSHOT.jar"
|
||||||
|
echo ""
|
||||||
|
echo "Or specify a custom host and port (e.g. for local testing):"
|
||||||
|
echo "java -jar target/client-1.0-SNAPSHOT.jar localhost 1870"
|
||||||
echo "--------------------------------------------------"
|
echo "--------------------------------------------------"
|
||||||
else
|
else
|
||||||
echo "Build Failed!"
|
echo "Build Failed!"
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,58 @@ public class Main extends JFrame {
|
||||||
private boolean myTurn = false;
|
private boolean myTurn = false;
|
||||||
private char mySymbol = ' '; // assigned by server implied turn
|
private char mySymbol = ' '; // assigned by server implied turn
|
||||||
|
|
||||||
public Main() {
|
public static void main(String[] args) {
|
||||||
|
String host = "dokploy.lona-development.org";
|
||||||
|
int port = 1870;
|
||||||
|
|
||||||
|
if (args.length > 0) {
|
||||||
|
host = args[0];
|
||||||
|
}
|
||||||
|
if (args.length > 1) {
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(args[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.err.println("Invalid port number provided, using default 1870");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String serverHost = host;
|
||||||
|
final int serverPort = port;
|
||||||
|
|
||||||
|
System.out.println("Starting TicTacToe Client...");
|
||||||
|
System.out.println("Target Server: " + serverHost + ":" + serverPort);
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
Main client = new Main(serverHost, serverPort);
|
||||||
|
client.setVisible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("Failed to start GUI: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance variables
|
||||||
|
private final String serverHost;
|
||||||
|
private final int serverPort;
|
||||||
|
|
||||||
|
public Main(String host, int port) {
|
||||||
super("TicTacToe Client");
|
super("TicTacToe Client");
|
||||||
|
this.serverHost = host;
|
||||||
|
this.serverPort = port;
|
||||||
|
|
||||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
setSize(400, 500);
|
setSize(400, 500);
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
initUI();
|
try {
|
||||||
setContentPane(mainPanel);
|
initUI();
|
||||||
|
setContentPane(mainPanel);
|
||||||
connectToServer();
|
connectToServer();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initUI() {
|
private void initUI() {
|
||||||
|
|
@ -95,21 +137,24 @@ public class Main extends JFrame {
|
||||||
private void connectToServer() {
|
private void connectToServer() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
// Using the specific domain provided
|
System.out.println("Attempting to connect to " + serverHost + ":" + serverPort);
|
||||||
socket = new Socket("dokploy.lona-development.org", 1870);
|
socket = new Socket(serverHost, serverPort);
|
||||||
out = new PrintWriter(socket.getOutputStream(), true);
|
out = new PrintWriter(socket.getOutputStream(), true);
|
||||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
|
||||||
|
System.out.println("Connected successfully!");
|
||||||
SwingUtilities.invokeLater(() -> statusLabel.setText("Connected to Server."));
|
SwingUtilities.invokeLater(() -> statusLabel.setText("Connected to Server."));
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
while ((line = in.readLine()) != null) {
|
while ((line = in.readLine()) != null) {
|
||||||
|
System.out.println("Received: " + line); // Log received messages
|
||||||
processMessage(line);
|
processMessage(line);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
System.err.println("Connection Error: " + e.getMessage());
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
statusLabel.setText("Connection Failed.");
|
||||||
JOptionPane.showMessageDialog(this, "Connection Error: " + e.getMessage());
|
JOptionPane.showMessageDialog(this, "Connection Error: " + e.getMessage());
|
||||||
statusLabel.setText("Disconnected.");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
@ -189,8 +234,4 @@ public class Main extends JFrame {
|
||||||
statusLabel.setText("Connected.");
|
statusLabel.setText("Connected.");
|
||||||
codeLabel.setText("Code: -");
|
codeLabel.setText("Code: -");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SwingUtilities.invokeLater(() -> new Main().setVisible(true));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
client/target/classes/com/lona/tictactoe/client/Main.class
Normal file
BIN
client/target/classes/com/lona/tictactoe/client/Main.class
Normal file
Binary file not shown.
BIN
client/target/client-1.0-SNAPSHOT.jar
Normal file
BIN
client/target/client-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
5
client/target/maven-archiver/pom.properties
Normal file
5
client/target/maven-archiver/pom.properties
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#Generated by Maven
|
||||||
|
#Tue Feb 10 13:27:55 CET 2026
|
||||||
|
artifactId=client
|
||||||
|
groupId=com.lona.tictactoe
|
||||||
|
version=1.0-SNAPSHOT
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
com/lona/tictactoe/client/Main.class
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
/home/collin/tictactoe/client/src/main/java/com/lona/tictactoe/client/Main.java
|
||||||
BIN
client/target/original-client-1.0-SNAPSHOT.jar
Normal file
BIN
client/target/original-client-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
5
server/target/maven-archiver/pom.properties
Normal file
5
server/target/maven-archiver/pom.properties
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#Generated by Maven
|
||||||
|
#Tue Feb 10 13:26:46 CET 2026
|
||||||
|
artifactId=server
|
||||||
|
groupId=com.lona.tictactoe
|
||||||
|
version=1.0-SNAPSHOT
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
com/lona/tictactoe/server/ClientHandler.class
|
||||||
|
com/lona/tictactoe/server/GameServer.class
|
||||||
|
com/lona/tictactoe/server/GameInstance.class
|
||||||
|
com/lona/tictactoe/server/GameManager.class
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/home/collin/tictactoe/server/src/main/java/com/lona/tictactoe/server/GameInstance.java
|
||||||
|
/home/collin/tictactoe/server/src/main/java/com/lona/tictactoe/server/ClientHandler.java
|
||||||
|
/home/collin/tictactoe/server/src/main/java/com/lona/tictactoe/server/GameServer.java
|
||||||
|
/home/collin/tictactoe/server/src/main/java/com/lona/tictactoe/server/GameManager.java
|
||||||
BIN
server/target/original-server-1.0-SNAPSHOT.jar
Normal file
BIN
server/target/original-server-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
BIN
server/target/server-1.0-SNAPSHOT.jar
Normal file
BIN
server/target/server-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
191
web-client/target/classes/static/css/style.css
Normal file
191
web-client/target/classes/static/css/style.css
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
:root {
|
||||||
|
--bg-color: #09090b;
|
||||||
|
--card-bg: #18181b;
|
||||||
|
--primary: #2563eb;
|
||||||
|
--secondary: #db2777;
|
||||||
|
--accent: #10b981;
|
||||||
|
--text: #e4e4e7;
|
||||||
|
--text-muted: #a1a1aa;
|
||||||
|
--border: #27272a;
|
||||||
|
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: 'Orbitron', sans-serif;
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: linear-gradient(to right, var(--primary), var(--secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
text-shadow: 0 0 30px rgba(37, 99, 235, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: var(--card-bg);
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'Orbitron', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 0 15px rgba(37, 99, 235, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background: var(--card-bg); /* Use card background for join button */
|
||||||
|
color: white; /* Ensure text is visible */
|
||||||
|
border: 1px solid var(--border); /* Add a border to distinguish it */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add hover effect for secondary button too */
|
||||||
|
.secondary:hover {
|
||||||
|
background: #27272a; /* Slightly lighter on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box; /* This ensures padding is included in width */
|
||||||
|
padding: 12px;
|
||||||
|
margin: 10px 0;
|
||||||
|
background: #27272a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: white;
|
||||||
|
font-family: 'Orbitron', sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus {
|
||||||
|
outline: 2px solid var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: 15px 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Game Interface */
|
||||||
|
.game-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: #27272a;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .value {
|
||||||
|
font-family: 'Orbitron', sans-serif;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
background: #27272a;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-family: 'Orbitron', sans-serif;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell:hover {
|
||||||
|
background: #3f3f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.x {
|
||||||
|
color: var(--secondary); /* Pink for X */
|
||||||
|
text-shadow: 0 0 10px rgba(219, 39, 119, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.o {
|
||||||
|
color: var(--accent); /* Green for O */
|
||||||
|
text-shadow: 0 0 10px rgba(16, 185, 129, 0.6);
|
||||||
|
}
|
||||||
155
web-client/target/classes/static/js/app.js
Normal file
155
web-client/target/classes/static/js/app.js
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
const menuPanel = document.getElementById('menu');
|
||||||
|
const gamePanel = document.getElementById('game');
|
||||||
|
const statusDiv = document.getElementById('connection-status');
|
||||||
|
const createBtn = document.getElementById('create-btn');
|
||||||
|
const joinBtn = document.getElementById('join-btn');
|
||||||
|
const joinInput = document.getElementById('join-code');
|
||||||
|
const displayCode = document.getElementById('display-code');
|
||||||
|
const turnStatus = document.getElementById('turn-status');
|
||||||
|
const surrenderBtn = document.getElementById('surrender-btn');
|
||||||
|
const cells = document.querySelectorAll('.cell');
|
||||||
|
|
||||||
|
let mySymbol = '';
|
||||||
|
let ws = null;
|
||||||
|
let currentCode = '';
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
ws = new WebSocket(`${protocol}//${window.location.host}/game`);
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
statusDiv.textContent = 'Connected to Server';
|
||||||
|
statusDiv.style.color = '#10b981';
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
statusDiv.textContent = 'Disconnected. Reconnecting...';
|
||||||
|
statusDiv.style.color = '#ef4444';
|
||||||
|
setTimeout(connect, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
handleMessage(event.data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(msg) {
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createBtn.addEventListener('click', () => {
|
||||||
|
send('CREATE');
|
||||||
|
});
|
||||||
|
|
||||||
|
joinBtn.addEventListener('click', () => {
|
||||||
|
const code = joinInput.value.trim();
|
||||||
|
if (code) send(`JOIN ${code}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
surrenderBtn.addEventListener('click', () => {
|
||||||
|
if (confirm('Are you sure you want to surrender?')) {
|
||||||
|
send('SURRENDER');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cells.forEach(cell => {
|
||||||
|
cell.addEventListener('click', () => {
|
||||||
|
const r = cell.dataset.r;
|
||||||
|
const c = cell.dataset.c;
|
||||||
|
send(`MOVE ${r} ${c}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleMessage(msg) {
|
||||||
|
console.log("Received:", msg);
|
||||||
|
const parts = msg.split(' ');
|
||||||
|
const cmd = parts[0];
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case 'GAME_CREATED':
|
||||||
|
currentCode = parts[1];
|
||||||
|
mySymbol = 'X';
|
||||||
|
enterGame();
|
||||||
|
updateStatus("Waiting for opponent...");
|
||||||
|
break;
|
||||||
|
case 'JOIN_SUCCESS':
|
||||||
|
currentCode = joinInput.value;
|
||||||
|
mySymbol = 'O';
|
||||||
|
enterGame();
|
||||||
|
updateStatus("Connected! Game starting soon...");
|
||||||
|
break;
|
||||||
|
case 'START':
|
||||||
|
updateStatus("Game Started! X goes first.");
|
||||||
|
break;
|
||||||
|
case 'BOARD':
|
||||||
|
const boardStr = msg.substring(6);
|
||||||
|
updateBoard(boardStr);
|
||||||
|
break;
|
||||||
|
case 'TURN':
|
||||||
|
const turn = parts[1];
|
||||||
|
if (turn === mySymbol) {
|
||||||
|
updateStatus(`Your Turn (${mySymbol})`, true);
|
||||||
|
} else {
|
||||||
|
updateStatus(`Opponent's Turn (${turn})`, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'WIN':
|
||||||
|
const winner = msg.substring(4);
|
||||||
|
alert(`Game Over! Winner: ${winner}`);
|
||||||
|
resetGame();
|
||||||
|
break;
|
||||||
|
case 'DRAW':
|
||||||
|
alert("Game Over! It's a draw!");
|
||||||
|
resetGame();
|
||||||
|
break;
|
||||||
|
case 'ERROR:':
|
||||||
|
alert(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterGame() {
|
||||||
|
menuPanel.classList.add('hidden');
|
||||||
|
gamePanel.classList.remove('hidden');
|
||||||
|
displayCode.textContent = currentCode;
|
||||||
|
// Clear board
|
||||||
|
cells.forEach(c => {
|
||||||
|
c.textContent = '';
|
||||||
|
c.className = 'cell';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetGame() {
|
||||||
|
menuPanel.classList.remove('hidden');
|
||||||
|
gamePanel.classList.add('hidden');
|
||||||
|
currentCode = '';
|
||||||
|
mySymbol = '';
|
||||||
|
joinInput.value = '';
|
||||||
|
statusDiv.textContent = 'Connected to Server';
|
||||||
|
statusDiv.style.color = '#10b981';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBoard(boardStr) {
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
if (i < boardStr.length) {
|
||||||
|
const char = boardStr.charAt(i);
|
||||||
|
cell.textContent = char === ' ' ? '' : char;
|
||||||
|
cell.className = 'cell';
|
||||||
|
if (char === 'X') cell.classList.add('x');
|
||||||
|
if (char === 'O') cell.classList.add('o');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus(text, isAction = false) {
|
||||||
|
turnStatus.textContent = text;
|
||||||
|
if (isAction) {
|
||||||
|
turnStatus.style.color = '#10b981'; // Green for 'your turn'
|
||||||
|
} else {
|
||||||
|
turnStatus.style.color = '#a1a1aa';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect();
|
||||||
61
web-client/target/classes/templates/index.html
Normal file
61
web-client/target/classes/templates/index.html
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TicTacToe</title>
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Roboto:wght@300;400&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>TicTacToe</h1>
|
||||||
|
|
||||||
|
<div id="connection-status" class="status-bar">Connecting to server...</div>
|
||||||
|
|
||||||
|
<!-- Menu Section -->
|
||||||
|
<div id="menu" class="panel">
|
||||||
|
<h2>Start Game</h2>
|
||||||
|
<div class="menu-controls">
|
||||||
|
<button id="create-btn" class="btn primary">Create New Game</button>
|
||||||
|
<div class="divider">OR</div>
|
||||||
|
<div class="join-group">
|
||||||
|
<input type="text" id="join-code" placeholder="Enter Game Code" maxlength="6">
|
||||||
|
<button id="join-btn" class="btn secondary">Join Game</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Game Section -->
|
||||||
|
<div id="game" class="panel hidden">
|
||||||
|
<div class="game-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Game Code:</span>
|
||||||
|
<span id="display-code" class="value">??????</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">Status:</span>
|
||||||
|
<span id="turn-status" class="value">Waiting...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="board" id="board">
|
||||||
|
<!-- Cells generated by JS -->
|
||||||
|
<div class="cell" data-r="0" data-c="0"></div>
|
||||||
|
<div class="cell" data-r="0" data-c="1"></div>
|
||||||
|
<div class="cell" data-r="0" data-c="2"></div>
|
||||||
|
<div class="cell" data-r="1" data-c="0"></div>
|
||||||
|
<div class="cell" data-r="1" data-c="1"></div>
|
||||||
|
<div class="cell" data-r="1" data-c="2"></div>
|
||||||
|
<div class="cell" data-r="2" data-c="0"></div>
|
||||||
|
<div class="cell" data-r="2" data-c="1"></div>
|
||||||
|
<div class="cell" data-r="2" data-c="2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="surrender-btn" class="btn danger">Surrender / Give Up</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
web-client/target/maven-archiver/pom.properties
Normal file
3
web-client/target/maven-archiver/pom.properties
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
artifactId=web-client
|
||||||
|
groupId=com.lona.tictactoe
|
||||||
|
version=1.0-SNAPSHOT
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
com/lona/tictactoe/web/TcpSession.class
|
||||||
|
com/lona/tictactoe/web/WebSocketConfig.class
|
||||||
|
com/lona/tictactoe/web/Application.class
|
||||||
|
com/lona/tictactoe/web/GameWebSocketHandler.class
|
||||||
|
com/lona/tictactoe/web/WebController.class
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
/home/collin/tictactoe/web-client/src/main/java/com/lona/tictactoe/web/TcpSession.java
|
||||||
|
/home/collin/tictactoe/web-client/src/main/java/com/lona/tictactoe/web/WebSocketConfig.java
|
||||||
|
/home/collin/tictactoe/web-client/src/main/java/com/lona/tictactoe/web/GameWebSocketHandler.java
|
||||||
|
/home/collin/tictactoe/web-client/src/main/java/com/lona/tictactoe/web/Application.java
|
||||||
|
/home/collin/tictactoe/web-client/src/main/java/com/lona/tictactoe/web/WebController.java
|
||||||
BIN
web-client/target/web-client-1.0-SNAPSHOT.jar
Normal file
BIN
web-client/target/web-client-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
BIN
web-client/target/web-client-1.0-SNAPSHOT.jar.original
Normal file
BIN
web-client/target/web-client-1.0-SNAPSHOT.jar.original
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue