feat: 1.0 stable api
This commit is contained in:
@@ -1,56 +1,66 @@
|
|||||||
const WebSocket = require('ws');
|
const WebSocket = require("ws");
|
||||||
const readline = require('readline');
|
const readline = require("readline");
|
||||||
|
|
||||||
const url = 'wss://connect4.abunchofknowitalls.com';
|
const DEFAULT_URL = "wss://connect4.abunchofknowitalls.com";
|
||||||
console.log(`Connecting to ${url}...`);
|
|
||||||
const ws = new WebSocket(url);
|
|
||||||
|
|
||||||
|
let ws;
|
||||||
let pingInterval;
|
let pingInterval;
|
||||||
|
|
||||||
ws.on('open', () => {
|
function startClient(serverUrl) {
|
||||||
console.log('Connected to server!');
|
console.log(`Connecting to ${serverUrl}...`);
|
||||||
console.log('Type a message and press Enter to send raw text.');
|
ws = new WebSocket(serverUrl);
|
||||||
|
|
||||||
|
ws.on("open", () => {
|
||||||
|
console.log("Connected to server!");
|
||||||
|
console.log("Type a message and press Enter to send raw text.");
|
||||||
// Keep the connection alive by sending a ping every 30 seconds
|
// Keep the connection alive by sending a ping every 30 seconds
|
||||||
pingInterval = setInterval(() => {
|
pingInterval = setInterval(() => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
ws.ping();
|
ws.ping();
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('message', (data) => {
|
ws.on("message", (data) => {
|
||||||
// data is usually a Buffer in 'ws', convert to string
|
// data is usually a Buffer in 'ws', convert to string
|
||||||
console.log('< ' + data.toString());
|
console.log("< " + data.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('pong', () => {
|
ws.on("pong", () => {
|
||||||
// console.log('Received pong');
|
// console.log('Received pong');
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('close', () => {
|
ws.on("close", () => {
|
||||||
console.log('Disconnected from server.');
|
console.log("Disconnected from server.");
|
||||||
if (pingInterval) clearInterval(pingInterval);
|
if (pingInterval) clearInterval(pingInterval);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('error', (err) => {
|
ws.on("error", (err) => {
|
||||||
console.error('WebSocket error:', err.message);
|
console.error("WebSocket error:", err.message);
|
||||||
if (pingInterval) clearInterval(pingInterval);
|
if (pingInterval) clearInterval(pingInterval);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
prompt: '> '
|
prompt: "> ",
|
||||||
});
|
});
|
||||||
|
|
||||||
rl.prompt();
|
rl.question(`Enter server address [${DEFAULT_URL}]: `, (answer) => {
|
||||||
|
const serverUrl = answer.trim() || DEFAULT_URL;
|
||||||
|
startClient(serverUrl);
|
||||||
|
|
||||||
rl.on('line', (line) => {
|
rl.on("line", (line) => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(line);
|
ws.send(line);
|
||||||
} else {
|
} else {
|
||||||
console.log('Socket not open. State:', ws.readyState);
|
const state = ws ? ws.readyState : "N/A";
|
||||||
|
console.log("Socket not open. State:", state);
|
||||||
}
|
}
|
||||||
|
rl.prompt();
|
||||||
|
});
|
||||||
|
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import asyncio
|
|||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
|
DEFAULT_SERVER_URL = "wss://connect4.abunchofknowitalls.com"
|
||||||
|
|
||||||
|
|
||||||
def calculate_move(opponent_move, board):
|
def calculate_move(opponent_move, board):
|
||||||
if opponent_move is not None:
|
if opponent_move is not None:
|
||||||
print(f"Opponent played column {opponent_move}")
|
print(f"Opponent played column {opponent_move}")
|
||||||
@@ -13,37 +16,53 @@ def calculate_move(opponent_move, board):
|
|||||||
async def gameloop(socket):
|
async def gameloop(socket):
|
||||||
board = [[None] * 6 for _ in range(7)]
|
board = [[None] * 6 for _ in range(7)]
|
||||||
while True: # While game is active, continually anticipate messages
|
while True: # While game is active, continually anticipate messages
|
||||||
message = (await socket.recv()).split(':') # Receive message from server
|
message = (await socket.recv()).split(":") # Receive message from server
|
||||||
|
|
||||||
match message[0]:
|
match message[0]:
|
||||||
case 'CONNECT':
|
case "CONNECT":
|
||||||
await socket.send('READY')
|
await socket.send("READY")
|
||||||
|
|
||||||
case 'GAME':
|
case "GAME":
|
||||||
if message[1] == 'START':
|
if message[1] == "START":
|
||||||
if message[2] == '1':
|
if message[2] == "1":
|
||||||
col = calculate_move(None, board) # calculate_move is some arbitrary function you have created to figure out the next move
|
col = calculate_move(
|
||||||
await socket.send(f'PLAY:{col}') # Send your move to the sever
|
None, board
|
||||||
if (message[1] == 'WINS') | (message[1] == 'LOSS') | (message[1] == 'DRAW') | (message[1] == 'TERMINATED'): # Game has ended
|
) # calculate_move is some arbitrary function you have created to figure out the next move
|
||||||
print(message[0]+":"+message[1])
|
await socket.send(f"PLAY:{col}") # Send your move to the sever
|
||||||
|
if (
|
||||||
|
(message[1] == "WINS")
|
||||||
|
| (message[1] == "LOSS")
|
||||||
|
| (message[1] == "DRAW")
|
||||||
|
| (message[1] == "TERMINATED")
|
||||||
|
): # Game has ended
|
||||||
|
print(message[0] + ":" + message[1])
|
||||||
board = [[None] * 6 for _ in range(7)]
|
board = [[None] * 6 for _ in range(7)]
|
||||||
await socket.send('READY')
|
await socket.send("READY")
|
||||||
|
|
||||||
case 'OPPONENT': # Opponent has gone; calculate next move
|
case "OPPONENT": # Opponent has gone; calculate next move
|
||||||
col = calculate_move(message[1], board) # Give your function your opponent's move
|
col = calculate_move(
|
||||||
await socket.send(f'PLAY:{col}') # Send your move to the sever
|
message[1], board
|
||||||
|
) # Give your function your opponent's move
|
||||||
|
await socket.send(f"PLAY:{col}") # Send your move to the sever
|
||||||
|
|
||||||
case 'ERROR':
|
case "ERROR":
|
||||||
print(f"{message[0]}: {':'.join(message[1:])}")
|
print(f"{message[0]}: {':'.join(message[1:])}")
|
||||||
|
|
||||||
await socket.close()
|
await socket.close()
|
||||||
|
|
||||||
async def join_server(username):
|
|
||||||
async with websockets.connect(f'wss://connect4.abunchofknowitalls.com', ping_interval=30, ping_timeout=30) as socket: # Establish websocket connection
|
async def join_server(username, server_url):
|
||||||
await socket.send(f'CONNECT:{username}')
|
async with websockets.connect(
|
||||||
|
server_url, ping_interval=30, ping_timeout=30
|
||||||
|
) as socket: # Establish websocket connection
|
||||||
|
await socket.send(f"CONNECT:{username}")
|
||||||
await gameloop(socket)
|
await gameloop(socket)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': # Program entrypoint
|
if __name__ == "__main__": # Program entrypoint
|
||||||
|
server_url = (
|
||||||
|
input(f"Enter server address [{DEFAULT_SERVER_URL}]: ").strip()
|
||||||
|
or DEFAULT_SERVER_URL
|
||||||
|
)
|
||||||
username = input("Enter username: ")
|
username = input("Enter username: ")
|
||||||
asyncio.run(join_server(username))
|
asyncio.run(join_server(username, server_url))
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@@ -423,9 +423,15 @@ async fn handle_connection(
|
|||||||
broadcast_message_all_observers(&observers, &message).await;
|
broadcast_message_all_observers(&observers, &message).await;
|
||||||
} else {
|
} else {
|
||||||
// Create next matches
|
// Create next matches
|
||||||
|
// TODO: Make this a function
|
||||||
for (i, id) in tourney.top_half.iter().enumerate() {
|
for (i, id) in tourney.top_half.iter().enumerate() {
|
||||||
let player1_addr = tourney.players.get(id).unwrap();
|
let player1_addr = tourney.players.get(id).unwrap();
|
||||||
let player2_addr = tourney.players.get(tourney.bottom_half.get(i).unwrap()).unwrap();
|
let player2_addr = tourney.players.get(tourney.bottom_half.get(i).unwrap());
|
||||||
|
|
||||||
|
if player2_addr.is_none() { continue; }
|
||||||
|
let player2_addr = player2_addr.unwrap();
|
||||||
|
|
||||||
|
// TODO: gen without collisions
|
||||||
let match_id: u32 = rand::rng().random_range(100000..=999999);
|
let match_id: u32 = rand::rng().random_range(100000..=999999);
|
||||||
let new_match = Arc::new(RwLock::new(Match::new(
|
let new_match = Arc::new(RwLock::new(Match::new(
|
||||||
match_id,
|
match_id,
|
||||||
@@ -475,11 +481,11 @@ async fn handle_connection(
|
|||||||
if !demo_mode {
|
if !demo_mode {
|
||||||
let connection = opponent.connection.clone();
|
let connection = opponent.connection.clone();
|
||||||
let column = column_parse.clone()?;
|
let column = column_parse.clone()?;
|
||||||
let waiting = *waiting_timeout.read().await * 1000 + (rand::rng().random_range(0..=500) - 250);
|
let waiting = *waiting_timeout.read().await as i64 * 1000 + (rand::rng().random_range(0..=500) - 250);
|
||||||
let matches_move = matches.clone();
|
let matches_move = matches.clone();
|
||||||
let match_id_move = current_match.id;
|
let match_id_move = current_match.id;
|
||||||
current_match.wait_thread = Some(tokio::spawn(async move {
|
current_match.wait_thread = Some(tokio::spawn(async move {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(waiting)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(waiting as u64)).await;
|
||||||
|
|
||||||
let mut matches_guard = matches_move.write().await;
|
let mut matches_guard = matches_move.write().await;
|
||||||
let mut current_match = matches_guard.get_mut(&match_id_move).unwrap().write().await;
|
let mut current_match = matches_guard.get_mut(&match_id_move).unwrap().write().await;
|
||||||
@@ -708,6 +714,15 @@ async fn handle_connection(
|
|||||||
matches.write().await.insert(match_id, new_match.clone());
|
matches.write().await.insert(match_id, new_match.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if text.starts_with("TOURNAMENT:WAIT:") {
|
||||||
|
if admin.read().await.is_none() || admin.read().await.unwrap() != addr {
|
||||||
|
let _ = send(&tx, "ERROR:INVALID:AUTH");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_timeout = text.split(":").collect::<Vec<&str>>()[2].parse::<f64>()?;
|
||||||
|
*waiting_timeout.write().await = (new_timeout * 1000.0) as u64;
|
||||||
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
let _ = send(&tx, "ERROR:UNKNOWN");
|
let _ = send(&tx, "ERROR:UNKNOWN");
|
||||||
|
|||||||
20
src/types.rs
20
src/types.rs
@@ -74,13 +74,23 @@ impl Tournament {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) {
|
pub fn next(&mut self) {
|
||||||
let first = *self.bottom_half.last().unwrap();
|
if self.is_completed {
|
||||||
let last = *self.top_half.last().unwrap();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.top_half[0] = first;
|
if self.top_half.len() <= 1 || self.bottom_half.is_empty() {
|
||||||
self.bottom_half[0] = last;
|
self.is_completed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if self.top_half[0] == 0 {
|
let last_from_top = self.top_half.pop().unwrap();
|
||||||
|
let first_from_bottom = self.bottom_half.remove(0);
|
||||||
|
|
||||||
|
self.top_half.insert(1, first_from_bottom);
|
||||||
|
self.bottom_half.push(last_from_top);
|
||||||
|
|
||||||
|
let expected_bottom_start = self.top_half.len() as u32;
|
||||||
|
if self.top_half[1] == 1 && self.bottom_half[0] == expected_bottom_start {
|
||||||
self.is_completed = true;
|
self.is_completed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user