feat: 2026 class will hopefully run better
This commit is contained in:
33
README.md
33
README.md
@@ -1,16 +1,35 @@
|
||||
# Connect4 Moderator - Server
|
||||
|
||||
A WebSocket server for the Connect4 Moderator.
|
||||
A WebSocket server for the RPI Minds & Machines class and the Connect4 Final Project.
|
||||
|
||||
An example client can be found in the [`example_client.py`](https://github.com/joshuafhiggins/connect4-moderator-server/blob/main/example_client.py) file.
|
||||
# To Build Your AI
|
||||
Download the [`gameloop.py`](https://github.com/joshuafhiggins/connect4-moderator-server/blob/main/gameloop.py) and [`agent.py`](https://github.com/joshuafhiggins/connect4-moderator-server/blob/main/agent.py) files. It is heavily encouraged you only make changes to the `agent.py` file
|
||||
|
||||
In order to run the example you need:
|
||||
In order to run your AI, you'll need:
|
||||
- Python 3
|
||||
- `pip install websockets`
|
||||
- `pip install pip-system-certs`
|
||||
- `pip install websockets` (Windows) or `pip3 install websockets` (Linux/macOS)
|
||||
- `pip install pip-system-certs` (Windows) or `pip3 install pip-system-certs` (Linux/macOS)
|
||||
|
||||
To run the example, run `python example_client.py`.
|
||||
To run the example, run `python gameloop.py`.
|
||||
|
||||
# To Watch Games
|
||||
The visual client for observing games/tournaments as well as managing games can be found at [`connect4-moderator-observer`](https://github.com/joshuafhiggins/connect4-moderator-observer).
|
||||
|
||||
The Google Sheet outlining the communication protocol can be found [here](https://docs.google.com/spreadsheets/d/1qPrNvB4-1jzvkaQXJcA1Z2OllpSQYmtMRfbOgSi4Yhw), for those of you who'd prefer to not write their AI in python
|
||||
# To Run the Server Locally
|
||||
- Install Git and the Rust programming language
|
||||
- `git clone https://github.com/joshuafhiggins/connect4-moderator-server.git`
|
||||
- `cd connect4-moderator-server`
|
||||
- `cargo run --release demo`
|
||||
|
||||
In this mode, you'll play against a player named `demo` who makes moves at random. If your AI makes invalid moves then your match is terminated and kicked from the server. If during the tournament you make an invalid move, you will instead immediately lose your game.
|
||||
|
||||
To connect to this server, use the address `ws://localhost:8080`
|
||||
|
||||
# For Future Maintainers:
|
||||
The Google Sheet outlining the communication protocol can be found [here](https://docs.google.com/spreadsheets/d/1qPrNvB4-1jzvkaQXJcA1Z2OllpSQYmtMRfbOgSi4Yhw), for those of you who'd prefer to not write their AI in python
|
||||
|
||||
A JavaScript debug client that text raw text as input and prints responses is provided,
|
||||
- `npm i`
|
||||
- `node debug_client.js`
|
||||
|
||||
I also apologize in advance.
|
||||
15
agent.py
Normal file
15
agent.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from typing import Union
|
||||
|
||||
class Agent:
|
||||
|
||||
def calculate_move(self, col: Union[int, None]) -> int:
|
||||
if col is None:
|
||||
# TODO: Determine what move to make when going first
|
||||
raise NotImplementedError("Agent needs to implement calculate_move")
|
||||
|
||||
# TODO: Determine what move to make, given the opponent just played in the 'col' index of the board
|
||||
raise NotImplementedError("Agent needs to implement calculate_move")
|
||||
|
||||
def reset(self):
|
||||
# TODO: Reset the Agent's internal states for a new game
|
||||
raise NotImplementedError("Agent needs to implement reset()")
|
||||
@@ -1,71 +0,0 @@
|
||||
import asyncio
|
||||
|
||||
import websockets
|
||||
|
||||
DEFAULT_SERVER_URL = "wss://connect4.abunchofknowitalls.com"
|
||||
|
||||
|
||||
def calculate_move(opponent_move):
|
||||
if opponent_move is not None:
|
||||
print(f"Opponent played column {opponent_move}")
|
||||
# TODO: Use the board variable to see and set the current state of the board
|
||||
# TODO: Implement your move calculation logic here instead
|
||||
return 0
|
||||
|
||||
def clear_board():
|
||||
# TODO: Create an empty board
|
||||
return
|
||||
|
||||
async def gameloop(socket):
|
||||
board = [[None] * 6 for _ in range(7)]
|
||||
while True: # While game is active, continually anticipate messages
|
||||
message = (await socket.recv()).split(":") # Receive message from server
|
||||
|
||||
match message[0]:
|
||||
case "CONNECT":
|
||||
await socket.send("READY")
|
||||
|
||||
case "GAME":
|
||||
if message[1] == "START":
|
||||
if message[2] == "1":
|
||||
col = calculate_move(
|
||||
None
|
||||
) # calculate_move is some arbitrary function you have created to figure out the next move
|
||||
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])
|
||||
clear_board()
|
||||
await socket.send("READY")
|
||||
|
||||
case "OPPONENT": # Opponent has gone; calculate next move
|
||||
col = calculate_move(
|
||||
message[1]
|
||||
) # Give your function your opponent's move
|
||||
await socket.send(f"PLAY:{col}") # Send your move to the sever
|
||||
|
||||
case "ERROR":
|
||||
print(f"{message[0]}: {':'.join(message[1:])}")
|
||||
|
||||
await socket.close()
|
||||
|
||||
|
||||
async def join_server(username, server_url):
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__": # Program entrypoint
|
||||
server_url = (
|
||||
input(f"Enter server address [{DEFAULT_SERVER_URL}]: ").strip()
|
||||
or DEFAULT_SERVER_URL
|
||||
)
|
||||
username = input("Enter username: ")
|
||||
asyncio.run(join_server(username, server_url))
|
||||
52
gameloop.py
Normal file
52
gameloop.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
|
||||
DEFAULT_SERVER_URL = "wss://connect4.abunchofknowitalls.com"
|
||||
|
||||
from agent import Agent
|
||||
|
||||
async def gameloop(socket):
|
||||
player = Agent()
|
||||
|
||||
while True: # While game is active, continually anticipate messages
|
||||
message = (await socket.recv()).split(":") # Receive message from server
|
||||
|
||||
match message[0]:
|
||||
case "CONNECT":
|
||||
await socket.send("READY")
|
||||
|
||||
case "GAME":
|
||||
if message[1] == "START":
|
||||
if message[2] == "1":
|
||||
# calculate_move is some arbitrary function you have created to figure out the next move
|
||||
col = player.calculate_move(None)
|
||||
await socket.send(f"PLAY:{col}")
|
||||
if (message[1] == "WINS") | (message[1] == "LOSS") | (message[1] == "DRAW") | (message[1] == "TERMINATED"):
|
||||
print(message[0] + ":" + message[1])
|
||||
player.reset()
|
||||
await socket.send("READY")
|
||||
|
||||
case "OPPONENT":
|
||||
# Opponent has gone; calculate next move
|
||||
col = player.calculate_move(message[1])
|
||||
await socket.send(f"PLAY:{col}") # Send your move to the sever
|
||||
|
||||
case "ERROR":
|
||||
print(f"{message[0]}: {':'.join(message[1:])}")
|
||||
|
||||
await socket.close()
|
||||
|
||||
|
||||
async def join_server(username, server_url):
|
||||
async with websockets.connect(server_url, ping_interval=30, ping_timeout=30) as socket:
|
||||
await socket.send(f"CONNECT:{username}")
|
||||
await gameloop(socket)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server_url = (
|
||||
input(f"Enter server address [{DEFAULT_SERVER_URL}]: ").strip()
|
||||
or DEFAULT_SERVER_URL
|
||||
)
|
||||
username = input("Enter username: ")
|
||||
asyncio.run(join_server(username, server_url))
|
||||
Reference in New Issue
Block a user