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
|
||||
|
||||
- `server/`: Java TCP Server (Port 1870). With Anticheat (Server-side validation).
|
||||
- `client/`: Standalone Java Swing Client. Connects to `dokploy.lona-development.org:1870`.
|
||||
- `server/`: Java TCP Server (Port 1870). Includes Anticheat logic (validates turns, moves, wins).
|
||||
- `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.
|
||||
|
||||
## Running with Docker Compose
|
||||
|
|
@ -16,28 +16,44 @@ To start the Server and Web Client:
|
|||
docker-compose up --build
|
||||
```
|
||||
|
||||
- **Server** runs on port `1870`.
|
||||
- **Server** runs on port `1870` (exposed).
|
||||
- **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
|
||||
./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
|
||||
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.
|
||||
- **JOIN <CODE>**: Joins an existing game.
|
||||
- **MOVE <ROW> <COL>**: Places your symbol.
|
||||
- **SURRENDER**: Forfeits the game.
|
||||
## Game Protocol (TCP 1870)
|
||||
|
||||
The server creates game sessions identified by a unique 6-character code.
|
||||
|
||||
- **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
|
||||
echo "--------------------------------------------------"
|
||||
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 ""
|
||||
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 "--------------------------------------------------"
|
||||
else
|
||||
echo "Build Failed!"
|
||||
|
|
|
|||
|
|
@ -23,16 +23,58 @@ public class Main extends JFrame {
|
|||
private boolean myTurn = false;
|
||||
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");
|
||||
this.serverHost = host;
|
||||
this.serverPort = port;
|
||||
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setSize(400, 500);
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
try {
|
||||
initUI();
|
||||
setContentPane(mainPanel);
|
||||
|
||||
connectToServer();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
|
|
@ -95,21 +137,24 @@ public class Main extends JFrame {
|
|||
private void connectToServer() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Using the specific domain provided
|
||||
socket = new Socket("dokploy.lona-development.org", 1870);
|
||||
System.out.println("Attempting to connect to " + serverHost + ":" + serverPort);
|
||||
socket = new Socket(serverHost, serverPort);
|
||||
out = new PrintWriter(socket.getOutputStream(), true);
|
||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
||||
System.out.println("Connected successfully!");
|
||||
SwingUtilities.invokeLater(() -> statusLabel.setText("Connected to Server."));
|
||||
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
System.out.println("Received: " + line); // Log received messages
|
||||
processMessage(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Connection Error: " + e.getMessage());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
statusLabel.setText("Connection Failed.");
|
||||
JOptionPane.showMessageDialog(this, "Connection Error: " + e.getMessage());
|
||||
statusLabel.setText("Disconnected.");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
|
@ -189,8 +234,4 @@ public class Main extends JFrame {
|
|||
statusLabel.setText("Connected.");
|
||||
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