p
This commit is contained in:
parent
0937fc35de
commit
1cb5c0923b
18 changed files with 496 additions and 4 deletions
14
ai-client/build.sh
Executable file
14
ai-client/build.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
echo "Building AI Client..."
|
||||||
|
mvn clean package
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "--------------------------------------------------"
|
||||||
|
echo "Build Successful!"
|
||||||
|
echo "Run AI Client:"
|
||||||
|
echo "java -jar target/ai-client-1.0-SNAPSHOT.jar [host] [port]"
|
||||||
|
echo "--------------------------------------------------"
|
||||||
|
else
|
||||||
|
echo "Build Failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
35
ai-client/pom.xml
Normal file
35
ai-client/pom.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.lona.tictactoe</groupId>
|
||||||
|
<artifactId>ai-client</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>com.lona.tictactoe.client.Main</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
406
ai-client/src/main/java/com/lona/tictactoe/client/Main.java
Normal file
406
ai-client/src/main/java/com/lona/tictactoe/client/Main.java
Normal file
|
|
@ -0,0 +1,406 @@
|
||||||
|
package com.lona.tictactoe.client;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class Main extends JFrame {
|
||||||
|
private Socket socket;
|
||||||
|
private PrintWriter out;
|
||||||
|
private BufferedReader in;
|
||||||
|
|
||||||
|
private CardLayout cardLayout = new CardLayout();
|
||||||
|
private JPanel mainPanel = new JPanel(cardLayout);
|
||||||
|
|
||||||
|
// Menu Components
|
||||||
|
private JTextField joinCodeField = new JTextField(10);
|
||||||
|
|
||||||
|
// Game Components
|
||||||
|
private JButton[] buttons = new JButton[9];
|
||||||
|
private JLabel statusLabel = new JLabel("Connecting...");
|
||||||
|
private JLabel codeLabel = new JLabel("Code: -");
|
||||||
|
private boolean myTurn = false;
|
||||||
|
private char mySymbol = ' '; // assigned by server implied turn
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String host = "38.242.130.81";
|
||||||
|
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() {
|
||||||
|
// --- MENU PANEL ---
|
||||||
|
JPanel menuPanel = new JPanel(new GridBagLayout());
|
||||||
|
JButton createBtn = new JButton("Create Game");
|
||||||
|
JButton joinBtn = new JButton("Join Game");
|
||||||
|
|
||||||
|
createBtn.addActionListener(e -> send("CREATE"));
|
||||||
|
joinBtn.addActionListener(e -> {
|
||||||
|
String code = joinCodeField.getText().trim();
|
||||||
|
if (!code.isEmpty())
|
||||||
|
send("JOIN " + code);
|
||||||
|
});
|
||||||
|
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
menuPanel.add(createBtn, gbc);
|
||||||
|
|
||||||
|
gbc.gridy = 1;
|
||||||
|
menuPanel.add(new JLabel("Enter Code:"), gbc);
|
||||||
|
gbc.gridy = 2;
|
||||||
|
menuPanel.add(joinCodeField, gbc);
|
||||||
|
gbc.gridy = 3;
|
||||||
|
menuPanel.add(joinBtn, gbc);
|
||||||
|
|
||||||
|
// --- GAME PANEL ---
|
||||||
|
JPanel gamePanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
JPanel topPanel = new JPanel(new GridLayout(2, 1));
|
||||||
|
topPanel.add(statusLabel);
|
||||||
|
topPanel.add(codeLabel);
|
||||||
|
gamePanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
JPanel boardPanel = new JPanel(new GridLayout(3, 3));
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
int finalI = i;
|
||||||
|
buttons[i] = new JButton("");
|
||||||
|
buttons[i].setFont(new Font("Arial", Font.BOLD, 40));
|
||||||
|
buttons[i].setFocusPainted(false);
|
||||||
|
buttons[i].addActionListener(e -> {
|
||||||
|
int r = finalI / 3;
|
||||||
|
int c = finalI % 3;
|
||||||
|
send("MOVE " + r + " " + c);
|
||||||
|
});
|
||||||
|
boardPanel.add(buttons[i]);
|
||||||
|
}
|
||||||
|
gamePanel.add(boardPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// --- BUTTONS PANEL ---
|
||||||
|
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 10, 0));
|
||||||
|
|
||||||
|
JButton leaveBtn = new JButton("Leave Lobby");
|
||||||
|
leaveBtn.setBackground(Color.GRAY);
|
||||||
|
leaveBtn.setForeground(Color.WHITE);
|
||||||
|
leaveBtn.addActionListener(e -> {
|
||||||
|
send("LEAVE");
|
||||||
|
resetGame();
|
||||||
|
});
|
||||||
|
|
||||||
|
JButton surrenderBtn = new JButton("Surrender");
|
||||||
|
surrenderBtn.setBackground(new Color(220, 53, 69)); // Bootstrap Danger Red
|
||||||
|
surrenderBtn.setForeground(Color.WHITE);
|
||||||
|
surrenderBtn.addActionListener(e -> send("SURRENDER"));
|
||||||
|
|
||||||
|
buttonPanel.add(leaveBtn);
|
||||||
|
buttonPanel.add(surrenderBtn);
|
||||||
|
|
||||||
|
gamePanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
mainPanel.add(menuPanel, "MENU");
|
||||||
|
mainPanel.add(gamePanel, "GAME");
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI Configuration
|
||||||
|
private static final String API_KEY = "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";
|
||||||
|
private static final String MODEL = "arcee-ai/trinity-large-preview:free";
|
||||||
|
private char[] boardState = new char[9]; // Keep track of board state
|
||||||
|
|
||||||
|
private void connectToServer() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
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());
|
||||||
|
// Don't reset to menu if we can't connect, let user see error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(String msg) {
|
||||||
|
if (out != null)
|
||||||
|
out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processMessage(String msg) {
|
||||||
|
SwingUtilities.invokeLater(() -> handleMessage(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMessage(String msg) {
|
||||||
|
String[] parts = msg.split(" ");
|
||||||
|
String cmd = parts[0];
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case "GAME_CREATED":
|
||||||
|
cardLayout.show(mainPanel, "GAME");
|
||||||
|
if (parts.length > 1) {
|
||||||
|
codeLabel.setText("Code: " + parts[1]);
|
||||||
|
mySymbol = 'X'; // Creator starts as X usually
|
||||||
|
statusLabel.setText("Game Created. Waiting for opponent...");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "JOIN_SUCCESS":
|
||||||
|
cardLayout.show(mainPanel, "GAME");
|
||||||
|
mySymbol = 'O'; // Joiner is O
|
||||||
|
statusLabel.setText("Joined Game.");
|
||||||
|
codeLabel.setText("Code: " + joinCodeField.getText());
|
||||||
|
break;
|
||||||
|
case "START":
|
||||||
|
statusLabel.setText("Game Started! X goes first.");
|
||||||
|
break;
|
||||||
|
case "RESTART":
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
buttons[i].setText("");
|
||||||
|
boardState[i] = ' ';
|
||||||
|
}
|
||||||
|
statusLabel.setText("Game Restarted! X goes first.");
|
||||||
|
break;
|
||||||
|
case "BOARD": // BOARD X O X ...
|
||||||
|
String boardStr = msg.substring(6); // remove "BOARD "
|
||||||
|
for (int i = 0; i < 9 && i < boardStr.length(); i++) {
|
||||||
|
char c = boardStr.charAt(i);
|
||||||
|
boardState[i] = c;
|
||||||
|
buttons[i].setText(String.valueOf(c));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "TURN":
|
||||||
|
if (parts.length > 1) {
|
||||||
|
char turn = parts[1].charAt(0);
|
||||||
|
if (turn == mySymbol) {
|
||||||
|
statusLabel.setText("AI Turn (" + mySymbol + ")... Thinking...");
|
||||||
|
makeAiMove();
|
||||||
|
} else {
|
||||||
|
statusLabel.setText("Opponent's Turn (" + turn + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "WIN":
|
||||||
|
String winner = msg.substring(4);
|
||||||
|
statusLabel.setText("Winner: " + winner);
|
||||||
|
// Auto RESTART if AI loses? Or wait for user? Let's just create a dialog but
|
||||||
|
// maybe auto-accept if we want seamless play? No, user requested seamless
|
||||||
|
// winning.
|
||||||
|
// Let's just wait for user to restart.
|
||||||
|
JOptionPane.showMessageDialog(this, "Winner: " + winner);
|
||||||
|
break;
|
||||||
|
case "DRAW":
|
||||||
|
statusLabel.setText("Draw!");
|
||||||
|
JOptionPane.showMessageDialog(this, "Draw!");
|
||||||
|
break;
|
||||||
|
case "OPPONENT_LEFT":
|
||||||
|
JOptionPane.showMessageDialog(this, "Opponent left the game.");
|
||||||
|
resetGame();
|
||||||
|
break;
|
||||||
|
case "ERROR:":
|
||||||
|
if (msg.contains("It is not your turn")) {
|
||||||
|
statusLabel.setText("Not your turn!");
|
||||||
|
} else {
|
||||||
|
// Start thinking again if move was invalid?
|
||||||
|
if (msg.contains("Invalid") || msg.contains("occupied")) {
|
||||||
|
makeAiMove(); // Retry
|
||||||
|
}
|
||||||
|
System.err.println("Server Error: " + msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeAiMove() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000); // Wait 2s
|
||||||
|
|
||||||
|
String prompt = buildPrompt();
|
||||||
|
System.out.println("Sending Prompt to AI:\n" + prompt);
|
||||||
|
|
||||||
|
String response = callOpenRouter(prompt);
|
||||||
|
System.out.println("AI Response: " + response);
|
||||||
|
|
||||||
|
// Parse response: expecting row, col
|
||||||
|
// The prompt will ask specifically for "row col" format
|
||||||
|
String[] coords = parseCoordinates(response);
|
||||||
|
if (coords != null) {
|
||||||
|
send("MOVE " + coords[0] + " " + coords[1]);
|
||||||
|
} else {
|
||||||
|
System.err.println("Failed to parse AI move. Retrying random...");
|
||||||
|
makeRandomMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
makeRandomMove();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeRandomMove() {
|
||||||
|
// Fallback: pick first empty spot
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
if (boardState[i] == ' ') {
|
||||||
|
int r = i / 3;
|
||||||
|
int c = i % 3;
|
||||||
|
send("MOVE " + r + " " + c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPrompt() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Play Tic-Tac-Toe as '").append(mySymbol).append("'.\n");
|
||||||
|
sb.append("Board:\n");
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
char c = boardState[i * 3 + j];
|
||||||
|
sb.append(c == ' ' ? '_' : c).append(" ");
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
sb.append(
|
||||||
|
"Respond ONLY with the coordinates of your next move in JSON format: {\"row\": <0-2>, \"col\": <0-2>}. Do not add any other text.");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String callOpenRouter(String prompt) throws IOException, InterruptedException {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
String jsonBody = "{"
|
||||||
|
+ "\"model\": \"" + MODEL + "\","
|
||||||
|
+ "\"messages\": [{\"role\": \"user\", \"content\": \""
|
||||||
|
+ prompt.replace("\n", "\\n").replace("\"", "\\\"") + "\"}],"
|
||||||
|
+ "\"temperature\": 0.2" // Lower temperature for more deterministic/structured output
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(java.net.URI.create("https://openrouter.ai/api/v1/chat/completions"))
|
||||||
|
.header("Authorization", "Bearer " + API_KEY)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
String body = response.body();
|
||||||
|
// System.out.println("DEBUG RAW BODY: " + body); // excessive logging, but
|
||||||
|
// helpful if needed
|
||||||
|
|
||||||
|
// Parse content
|
||||||
|
int contentIndex = body.indexOf("\"content\":");
|
||||||
|
if (contentIndex != -1) {
|
||||||
|
int start = body.indexOf("\"", contentIndex + 10) + 1;
|
||||||
|
// Find the end quote, handling escaped quotes
|
||||||
|
int end = start;
|
||||||
|
while (true) {
|
||||||
|
end = body.indexOf("\"", end + 1);
|
||||||
|
if (body.charAt(end - 1) != '\\') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String content = body.substring(start, end);
|
||||||
|
return content.replace("\\n", "\n").replace("\\\"", "\"");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] parseCoordinates(String text) {
|
||||||
|
// Look for JSON pattern first: "row": 1, "col": 2
|
||||||
|
Pattern jsonPattern = Pattern.compile("\"row\"\\s*:\\s*(\\d)\\s*,\\s*\"col\"\\s*:\\s*(\\d)");
|
||||||
|
Matcher m = jsonPattern.matcher(text);
|
||||||
|
if (m.find()) {
|
||||||
|
return new String[] { m.group(1), m.group(2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: strictly row col numbers
|
||||||
|
m = Pattern.compile("(\\d)\\s+(\\d)").matcher(text);
|
||||||
|
if (m.find()) {
|
||||||
|
return new String[] { m.group(1), m.group(2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// One last fallback: just find any two digits
|
||||||
|
m = Pattern.compile("(\\d)[^\\d]+(\\d)").matcher(text);
|
||||||
|
if (m.find()) {
|
||||||
|
return new String[] { m.group(1), m.group(2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetGame() {
|
||||||
|
cardLayout.show(mainPanel, "MENU");
|
||||||
|
for (JButton btn : buttons)
|
||||||
|
btn.setText("");
|
||||||
|
for (int i = 0; i < 9; i++)
|
||||||
|
boardState[i] = ' ';
|
||||||
|
statusLabel.setText("Connected.");
|
||||||
|
codeLabel.setText("Code: -");
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ai-client/target/ai-client-1.0-SNAPSHOT.jar
Normal file
BIN
ai-client/target/ai-client-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
BIN
ai-client/target/classes/com/lona/tictactoe/client/Main.class
Normal file
BIN
ai-client/target/classes/com/lona/tictactoe/client/Main.class
Normal file
Binary file not shown.
5
ai-client/target/maven-archiver/pom.properties
Normal file
5
ai-client/target/maven-archiver/pom.properties
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#Generated by Maven
|
||||||
|
#Tue Feb 10 14:17:06 CET 2026
|
||||||
|
artifactId=ai-client
|
||||||
|
groupId=com.lona.tictactoe
|
||||||
|
version=1.0-SNAPSHOT
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
com/lona/tictactoe/client/Main.class
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
/home/collin/tictactoe/ai-client/src/main/java/com/lona/tictactoe/client/Main.java
|
||||||
BIN
ai-client/target/original-ai-client-1.0-SNAPSHOT.jar
Normal file
BIN
ai-client/target/original-ai-client-1.0-SNAPSHOT.jar
Normal file
Binary file not shown.
|
|
@ -55,6 +55,7 @@ public class GameInstance {
|
||||||
|
|
||||||
if (checkWin(symbol)) {
|
if (checkWin(symbol)) {
|
||||||
finished = true;
|
finished = true;
|
||||||
|
lastWinner = symbol;
|
||||||
broadcast("WIN " + symbol);
|
broadcast("WIN " + symbol);
|
||||||
} else if (checkDraw()) {
|
} else if (checkDraw()) {
|
||||||
finished = true;
|
finished = true;
|
||||||
|
|
@ -72,13 +73,32 @@ public class GameInstance {
|
||||||
char loser = (player == playerX) ? 'X' : 'O';
|
char loser = (player == playerX) ? 'X' : 'O';
|
||||||
char winner = (loser == 'X') ? 'O' : 'X';
|
char winner = (loser == 'X') ? 'O' : 'X';
|
||||||
broadcast("WIN " + winner + " (Surrender)");
|
broadcast("WIN " + winner + " (Surrender)");
|
||||||
|
lastWinner = winner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private char lastWinner = ' ';
|
||||||
|
|
||||||
public synchronized void restart() {
|
public synchronized void restart() {
|
||||||
|
// Determine who starts: The loser of the previous game.
|
||||||
|
// If lastWinner was 'X', then 'O' (the loser) starts.
|
||||||
|
// If lastWinner was 'O', then 'X' (the loser) starts.
|
||||||
|
// If it was a draw or new game, default to 'X'.
|
||||||
|
|
||||||
|
if (lastWinner == 'X') {
|
||||||
|
currentTurn = 'O';
|
||||||
|
} else if (lastWinner == 'O') {
|
||||||
|
currentTurn = 'X';
|
||||||
|
} else {
|
||||||
|
currentTurn = 'X'; // Default for Draw/New
|
||||||
|
}
|
||||||
|
|
||||||
|
lastWinner = ' '; // Reset for next game
|
||||||
|
finished = false;
|
||||||
|
|
||||||
|
// Clear board
|
||||||
for (char[] row : board)
|
for (char[] row : board)
|
||||||
Arrays.fill(row, ' ');
|
Arrays.fill(row, ' ');
|
||||||
currentTurn = 'X';
|
|
||||||
finished = false;
|
|
||||||
broadcast("RESTART");
|
broadcast("RESTART");
|
||||||
sendBoard();
|
sendBoard();
|
||||||
broadcast("TURN " + currentTurn);
|
broadcast("TURN " + currentTurn);
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
||||||
#Generated by Maven
|
#Generated by Maven
|
||||||
#Tue Feb 10 14:07:14 CET 2026
|
#Tue Feb 10 14:16:42 CET 2026
|
||||||
artifactId=server
|
artifactId=server
|
||||||
groupId=com.lona.tictactoe
|
groupId=com.lona.tictactoe
|
||||||
version=1.0-SNAPSHOT
|
version=1.0-SNAPSHOT
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -30,9 +30,11 @@ public class TcpSession extends Thread {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
System.out.println("TcpSession: Connecting to " + host + ":" + port);
|
||||||
socket = new Socket(host, port);
|
socket = new Socket(host, port);
|
||||||
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("TcpSession: Connected successfully");
|
||||||
|
|
||||||
// Flush queue
|
// Flush queue
|
||||||
String queued;
|
String queued;
|
||||||
|
|
@ -46,8 +48,16 @@ public class TcpSession extends Thread {
|
||||||
session.sendMessage(new TextMessage(line));
|
session.sendMessage(new TextMessage(line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (java.net.ConnectException ce) {
|
||||||
|
System.err.println("TcpSession: Failed to connect to game server: " + ce.getMessage());
|
||||||
|
} catch (java.net.SocketException se) {
|
||||||
|
if (running) {
|
||||||
|
// Only print if we didn't initiate the close
|
||||||
|
System.err.println("TcpSession: Socket closed unexpectedly: " + se.getMessage());
|
||||||
|
se.printStackTrace();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// connection lost or failed
|
System.err.println("TcpSession: IO Error: " + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
close();
|
close();
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue