Compare commits

...

37 Commits

Author SHA1 Message Date
e599722e45 added client for testing connection and events 2025-11-15 14:44:44 +01:00
83a73ed34c Added tests 2025-11-13 15:19:26 +01:00
Hatvani Tamás
7c27d47935 Merge branch 'master' into Server/websocket 2025-11-12 19:31:55 +01:00
Hatvani Tamás
4ffec0f05d Add name to checkout step in dispatcher.yml 2025-11-12 19:27:29 +01:00
Hatvani Tamás
da3b1c8c00 Add checkout action to dispatcher workflow 2025-11-12 19:13:14 +01:00
Hatvani Tamás
a497f8554f Add checkout action to dispatcher workflow 2025-11-12 19:12:47 +01:00
94b4d727a6 Merge branch 'master' into Server/websocket 2025-11-12 19:04:41 +01:00
39273908ac removed checkout to potentially fix an error with test_data.log 2025-11-12 18:58:36 +01:00
Hatvani Tamás
7be3c928b7 Display test_data.log in workflow script
Added command to display contents of test_data.log
2025-11-12 18:54:06 +01:00
Hatvani Tamás
f8c14cc268 Enhance logging for data upload process
Added print statements to log data being uploaded to each tab.
2025-11-12 18:50:01 +01:00
Hatvani Tamás
566d72c2d6 Merge pull request #7 from htamas1210/Workflow
Workflow
2025-11-12 18:45:08 +01:00
ad26d22281 indentation error in python script 2025-11-12 18:44:00 +01:00
352b4e57d7 rewrote the python script
the script now checks for project name on every line read from the log
file and appends it to a list named by the project, if the branch is
master then it appends all lines into one list, then it checks if there
is any data in the list and uploads them to the correct tab that matches
the project name or master
2025-11-12 18:42:17 +01:00
5696128ac9 handling differenk kinds of server messages, and matchmaking basics 2025-11-12 17:11:23 +01:00
c2e29415c9 added new event system to handle different client events in game and on connection 2025-11-12 15:54:03 +01:00
ff68d9d2d9 new server messages 2025-11-12 15:52:55 +01:00
281f496c83 removed these files, ready to start working on event system messages 2025-11-12 15:14:07 +01:00
532c5a42ea added anyhow and rand crate 2025-11-12 15:13:01 +01:00
73624baf90 added server event enum 2025-11-12 14:01:13 +01:00
d8861b66b2 using serde feature from uuid, so i can add an id to a struct definition 2025-11-12 13:25:40 +01:00
4123a710cf first phase of rewrite done, server stores connections with an id, currently works as a hub, next phase to handle custom message types 2025-11-12 12:49:42 +01:00
4daa21e8bf Started to rewrite the project to handle connection storing and message types, added v4 for uuid crate 2025-11-12 11:28:14 +01:00
Hatvani Tamás
cdd8b45da8 Merge pull request #6 from htamas1210/Engine/movegen-utils
Engine/movegen utils
2025-11-12 11:08:36 +01:00
503cb23015 accepted connection and sending data both ways 2025-11-11 15:25:42 +01:00
Varga Dávid Lajos
6006442f90 added '#[inline(always)]' annotation to hot functions: utils::pop_lsb, utils::pop_msb 2025-11-11 14:23:30 +01:00
Varga Dávid Lajos
eebdfdbee2 implemented utility function: bitboard::utils::notation_from_square_number 2025-11-11 14:21:11 +01:00
Varga Dávid Lajos
a6aba8801e implemented utility function: bitboard::utils::pop_msb 2025-11-11 14:16:33 +01:00
Varga Dávid Lajos
8d1300d7e2 implemented utility function: bitboard::utils::pop_lsb 2025-11-11 14:14:39 +01:00
Varga Dávid Lajos
d0e6ce81ce fixed bad test values in test utils::tests::pop_lsb_test 2025-11-11 14:06:43 +01:00
Varga Dávid Lajos
f3dea86ded fixed typo in test utils::tests::pop_msb_test 2025-11-11 14:05:31 +01:00
Varga Dávid Lajos
092ed19104 added frame and tests for function: bitboard::util::notation_from_square_number 2025-11-11 13:59:53 +01:00
Varga Dávid Lajos
f8894bbdff added frame and tests for function: bitboard::util::pop_msb 2025-11-11 13:51:14 +01:00
Varga Dávid Lajos
258a8a0da9 added frame and tests for function: bitboard::util::pop_lsb 2025-11-11 13:39:26 +01:00
8ffbfdc63f basic listener implemented, addedd modules, working on handling connections 2025-11-11 13:35:34 +01:00
Varga Dávid Lajos
dca6eac3ba added file bitboard/utils.rs 2025-11-11 12:50:53 +01:00
8f9a48fa96 rendszerterv v1 2025-11-11 12:40:29 +01:00
Hatvani Tamás
5ee797e1f8 Add conditional logging for master branch 2025-11-11 12:32:03 +01:00
17 changed files with 1310 additions and 22 deletions

View File

@@ -16,6 +16,9 @@ jobs:
server: ${{ steps.check.outputs.server }}
ui: ${{ steps.check.outputs.ui }}
steps:
- name: checkout repository
uses: actions/checkout@v4
- name: Determine which tests to run
id: check
run: |

View File

@@ -9,8 +9,6 @@ jobs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run Engine tests
run: |
bash .github/workflows/test.sh engine/

View File

@@ -9,8 +9,6 @@ jobs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run Server tests
run: |
bash .github/workflows/test.sh server/

View File

@@ -30,8 +30,13 @@ echo "$PROJECT_NAME" > "$LOG_FILE"
awk '/^running [0-9]+ test[s]?$/,/^$/' full_test_output.log >> "$LOG_FILE"
# --- APPEND TO GLOBAL LOG (in repo root) ---
if [[ $(git rev-parse --abbrev-ref HEAD) == "master" ]]; then
echo "master" >> $FINAL_LOG
fi
cat "$LOG_FILE" >> "$FINAL_LOG"
# --- SUMMARY ---
echo ">>> Test output extracted to $PROJECT_PATH/$LOG_FILE"
echo ">>> Appended to $FINAL_LOG"
cat $(git rev-parse --show-toplevel)/test_data.log

View File

@@ -9,8 +9,6 @@ jobs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run UI tests
run: |
bash .github/workflows/test.sh ui/

View File

@@ -20,33 +20,77 @@ jobs:
echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > service_account.json
python <<'PYCODE'
import gspread, json, time, subprocess
import gspread, json, subprocess
# credentials
creds = json.load(open("service_account.json"))
gc = gspread.service_account_from_dict(creds)
sh = gc.open_by_key("${{ secrets.SPREADSHEET_ID }}")
v = subprocess.run(['git','rev-parse','--show-toplevel'], capture_output=True).stdout.decode().strip()
print(f"{v}/test_data.log")
def writeRowsToSpreadsheet(data_list, worksheet):
existing_rows = len(worksheet.get_all_values())
start_row = existing_rows + 3
rows_to_append = [row.split() for row in data_list]
print("rows to append")
print(f"{rows_to_append}")
for i, row in enumerate(rows_to_append):
worksheet.insert_row(row, start_row + i)
with open(f"{v}/test_data.log", "r") as f:
lines = [line.strip() for line in f if line.strip()]
isMaster = False
project = lines[0].lower()
worksheet = sh.worksheet(project)
if project == "master":
isMaster = True
engine_data = []
server_data = []
ui_data = []
master_data = []
# project name
data = lines[1:]
for entry in lines:
if not isMaster and entry == "engine":
project = "engine"
elif not isMaster and entry == "server":
project = "server"
elif not isMaster and entry == "ui":
project = "ui"
#blank rows
existing_rows = len(worksheet.get_all_values())
start_row = existing_rows + 3
if project == "engine" and entry != "engine":
engine_data.append(entry)
elif project == "server" and entry != "server":
server_data.append(entry)
elif project == "ui" and entry != "ui":
ui_data.append(entry)
elif project == "master" and entry != "master":
master_data.append(entry)
# Split data into columns (by spaces)
rows_to_append = [row.split() for row in data]
print("PRINTING FILTERED DATA\n\n")
print(f"engine\n{engine_data}")
print(f"server\n{server_data}")
print(f"ui\n{ui_data}")
print(f"master\n{master_data}")
print("\n\n\n")
for i, row in enumerate(rows_to_append):
worksheet.insert_row(row, start_row + i)
if isMaster and len(master_data) != 0:
print("uploading to master tab")
worksheet = sh.worksheet("master")
writeRowsToSpreadsheet(master_data, worksheet)
exit(0)
if len(engine_data) != 0:
print("uploading to engine tab")
writeRowsToSpreadsheet(engine_data, sh.worksheet("engine"))
if len(server_data) != 0:
print("uploading to server tab")
writeRowsToSpreadsheet(server_data, sh.worksheet("server"))
if len(ui_data) != 0:
print("uploading to ui tab")
writeRowsToSpreadsheet(ui_data, sh.worksheet("ui"))
print(f"Uploaded {len(rows_to_append)} rows to '{project}' tab.")
PYCODE

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

View File

@@ -0,0 +1,287 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0" version="28.2.9">
<diagram name="Page-1" id="9d-IwT1pk2NoWoP7MTJv">
<mxGraphModel dx="1534" dy="751" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="9999" pageHeight="9999" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-1" target="IaA3IaIvg4qVMEhu7ya9-36" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-1" value="App megnyitása" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="120" y="50" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-9" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="540" y="120" />
<mxPoint x="370" y="120" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-15" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-18" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="670" y="180" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-21" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-6" value="&lt;div&gt;főmenü&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="480" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-9" target="IaA3IaIvg4qVMEhu7ya9-23" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-9" value="Lokális játék hosztolása" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="310" y="210" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-32" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-11" target="IaA3IaIvg4qVMEhu7ya9-31" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-11" value="&lt;div&gt;Lokális játékba csatlakozni&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="460" y="220" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-18" target="4rzJeeoM2DMZDjvMm2AJ-12" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-18" value="Online jatek&lt;br&gt;(sorba allas)" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="695" y="200" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="IaA3IaIvg4qVMEhu7ya9-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-28" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="IaA3IaIvg4qVMEhu7ya9-27" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="IaA3IaIvg4qVMEhu7ya9-27">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-6" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-7">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="770" y="80" />
<mxPoint x="1000" y="80" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-20" value="&lt;div&gt;Beallitasok&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="650" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-23" target="4rzJeeoM2DMZDjvMm2AJ-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-23" value="&lt;div&gt;Szerver peldany elinditasa&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="130" y="150" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-25" target="IaA3IaIvg4qVMEhu7ya9-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-25" target="iJwoCWMJ6fIoSaqHo3aH-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-25" value="varakozas az ellenfelre" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="130" y="390" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-27" value="lokalis jatekhoz port megvaltoztatasa" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="830" y="10" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-29" target="4rzJeeoM2DMZDjvMm2AJ-32" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-29" value="&lt;div&gt;jatek elkezdése&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="400" y="850" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-34" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-31" target="IaA3IaIvg4qVMEhu7ya9-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-31" value="&lt;div&gt;lokalis halozaton szerver ip:port megadasa&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="460" y="360" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-33" target="IaA3IaIvg4qVMEhu7ya9-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-33" value="csatlakozás" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="290" y="360" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-36" target="IaA3IaIvg4qVMEhu7ya9-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-36" value="user struct letrehozasa a app-ban" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-4" target="4rzJeeoM2DMZDjvMm2AJ-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-4" value="hostolo kapcsolodik a szerverehez" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="130" y="230" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-7" target="IaA3IaIvg4qVMEhu7ya9-25" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-7" value="eszköz ip cím lekérése a csatlakozáshoz" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="130" y="310" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-12" target="4rzJeeoM2DMZDjvMm2AJ-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-12" value="&lt;div&gt;csatlakozás a központi szerverhez&lt;/div&gt;(tudjuk az ip cimet)" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="690" y="300" width="130" height="70" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-16" target="4rzJeeoM2DMZDjvMm2AJ-23" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-16" target="4rzJeeoM2DMZDjvMm2AJ-26" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-16" value="sorban állás szabad játékra várva" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="695" y="480" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-20" target="4rzJeeoM2DMZDjvMm2AJ-16" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-20" value="a szerver eltárolja az uj jatekost mint aktiv felhasznalo a listaban" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="700" y="390" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-23" value="a szerveren bekerultunk a varolistaba" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="850" y="465" width="155" height="90" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-26" target="4rzJeeoM2DMZDjvMm2AJ-28" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-26" value="&lt;div&gt;megjelent meg egy jatekos a varolistan&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="695" y="570" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-30" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-28" target="IaA3IaIvg4qVMEhu7ya9-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-28" value="ket jatekos ossze vonasa egy meccsbe. Kivesszuk oket a varolistabol es egy Match structba osszevonjuk oket" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="682.5" y="660" width="145" height="120" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-35" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-32" target="4rzJeeoM2DMZDjvMm2AJ-34" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-32" value="&lt;div&gt;fehér és fekete oldal eldöntése&lt;/div&gt;&lt;div&gt;(szerver által, érme dobással)&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="390" y="950" width="140" height="80" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-34" target="4rzJeeoM2DMZDjvMm2AJ-36" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-34" value="játék ui megjelenítése" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="400" y="1060" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-36" target="4rzJeeoM2DMZDjvMm2AJ-38" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-36" value="lehetséges lépések lekérése a tábla állapot alapján a szerver / engine-től" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="390" y="1160" width="140" height="90" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-41" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-38" target="4rzJeeoM2DMZDjvMm2AJ-40" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-38" value="a ui lokalisan eltarolja ezt az adot a következő körig" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="400" y="1280" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-2" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-40" target="iJwoCWMJ6fIoSaqHo3aH-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-19" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-40" target="iJwoCWMJ6fIoSaqHo3aH-18">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-40" value="a jatekos lep a egy babual. Ekkor ellenorizzuk hogy szabad e volt ezt a léppést megtennie" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="390" y="1370" width="140" height="90" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-1" target="iJwoCWMJ6fIoSaqHo3aH-11">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-1" target="iJwoCWMJ6fIoSaqHo3aH-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-1" value="ha szabad volt elküldjük a lépést a szervernek" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="400" y="1490" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-4" value="felbontás megváltoztatása" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="830" y="90" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-7" value="teljes képernyő" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="1000" y="50" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-9" value="ai api kulcs megadása" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="1000" y="140" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-21" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-11" target="iJwoCWMJ6fIoSaqHo3aH-20">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-11" value="&lt;div&gt;ellenfél ui frissítése&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="400" y="1580" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-13" value="&lt;div&gt;{&lt;/div&gt;&lt;div&gt;from:str,&lt;/div&gt;&lt;div&gt;to: str&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;" style="rhombus;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="560" y="1455" width="140" height="130" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-15" value="Ip cím megjelenítése a könnyebb csatlakozás érdekében" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="20" y="490" width="120" height="150" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-18" value="a ui végig megy az adatokon, amit visszakapott a szervertől a kör elején" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="590" y="1290" width="170" height="110" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-23" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-20" target="iJwoCWMJ6fIoSaqHo3aH-22">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-25" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-20" target="iJwoCWMJ6fIoSaqHo3aH-24">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-20">
<mxGeometry relative="1" as="geometry">
<mxPoint x="640" y="1860" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-20" value="ellenőrizzük, hogy nyert-e a játékos vagy döntetlen lett-e" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="395" y="1670" width="130" height="70" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-22" value="a szerver megkapja a tábla állást és elküldi az enginnek ami ad egy választ" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="560" y="1647.5" width="200" height="115" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-24" target="4rzJeeoM2DMZDjvMm2AJ-36">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="360" y="1820" />
<mxPoint x="360" y="1205" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-24" value="kör átadása az ellenfélnek" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="400" y="1790" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-28" target="iJwoCWMJ6fIoSaqHo3aH-29">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-28" value="játék befejezése, nyertes megjelnítése,&amp;nbsp;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="580" y="1860" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-29" value="a játékosokat vissza küldjük a menübe, a szerver oldalon töröljük a meccset a listából, és töröljük&amp;nbsp; a játékosokat a meccs közben lévő listából" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="560" y="1940" width="160" height="120" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -1 +1,2 @@
mod attackmaps;
mod attackmaps;
mod utils;

View File

@@ -0,0 +1,99 @@
#[inline(always)]
pub fn pop_lsb(value: &mut u64) -> usize {
let idx = value.trailing_zeros() as usize;
*value &= !(1 << idx);
return idx;
}
#[inline(always)]
pub fn pop_msb(value: &mut u64) -> usize {
let idx = 63 - value.leading_zeros() as usize;
*value &= !(1 << idx);
return idx;
}
const RANK_NUMBERS: [char; 8] = ['1', '2', '3', '4', '5', '6', '7', '8'];
const FILE_LETTERS: [char; 8] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
pub fn notation_from_square_number(sq: u8) -> String {
let row = sq / 8;
let col = sq % 8;
let mut notation = String::new();
let row_not = RANK_NUMBERS[row as usize];
let col_not = FILE_LETTERS[col as usize];
notation.push(col_not);
notation.push(row_not);
return notation;
}
// <----- TESTS ----->
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pop_lsb_test() {
// test setup
let test_values: [u64; 6] = [
0x8000_0000_0000_0000,
0x4E91_CF05_713E_451B,
0xD588_2D58_6962_34B0,
0x9581_3335_DCAB_1DD4,
0xBEAC_DBE0_903A_AC00,
0x01E8_C895_A6F0_0000
];
let expected_values: [usize; 6] = [63, 0, 4, 2, 10, 20];
// tests
for index in 0..6 {
let mut test_value = test_values[index];
assert_eq!(pop_lsb(&mut test_value), expected_values[index])
}
}
#[test]
fn pop_msb_test() {
// test setup
let test_values: [u64; 6] = [
0x86D6_8EB0_96A8_8D1C,
0x0000_0000_0000_0001,
0x3809_24AF_A7AE_8129,
0x0277_DA36_3B31_86D9,
0x0000_C1C3_201C_0DB1,
0x0000_0203_0DE4_E944
];
let expected_values: [usize; 6] = [63, 0, 61, 57, 47, 41];
// tests
for index in 0..6 {
let mut test_value = test_values[index];
assert_eq!(pop_msb(&mut test_value), expected_values[index])
}
}
#[test]
fn notation_from_square_number_test() {
// test setup
let square_indices: [u8; 8] = [1, 12, 22, 27, 32, 47, 53, 58];
let notations: [String; 8] = [
String::from("b1"),
String::from("e2"),
String::from("g3"),
String::from("d4"),
String::from("a5"),
String::from("h6"),
String::from("f7"),
String::from("c8")
];
// tests
for index in 0..8 {
let notation = notation_from_square_number(square_indices[index].clone());
assert_eq!(notation, notations[index]);
}
}
}

View File

@@ -4,3 +4,17 @@ version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.21"
tungstenite = "0.21"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures-util = "0.3.31"
url = "2.5.7"
uuid = {version = "1.18.1", features = ["v4", "serde"] }
anyhow = "1.0.100"
rand = "0.9.2"
[[bin]]
name = "client"
path = "src/bin/client.rs"

208
server/src/bin/client.rs Normal file
View File

@@ -0,0 +1,208 @@
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::io::{self, Write};
use tokio_tungstenite::{connect_async, tungstenite::Message};
use url::Url;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum ClientMessage {
Join { username: String },
FindMatch,
Move { from: String, to: String },
Resign,
Chat { text: String },
}
#[derive(Serialize, Deserialize, Debug)]
struct ServerMessage {
#[serde(rename = "type")]
message_type: String,
player_id: Option<String>,
match_id: Option<String>,
opponent: Option<String>,
color: Option<String>,
reason: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Knightly Chess Client");
println!("========================");
// Get server address from user
print!("Enter server address [ws://127.0.0.1:9001]: ");
io::stdout().flush()?;
let mut server_addr = String::new();
io::stdin().read_line(&mut server_addr)?;
let server_addr = server_addr.trim();
let server_addr = if server_addr.is_empty() {
"ws://127.0.0.1:9001".to_string()
} else {
server_addr.to_string()
};
// Connect to server
println!("Connecting to {}...", server_addr);
let url = Url::parse(&server_addr)?;
let (ws_stream, _) = connect_async(url).await?;
println!("Connected to server!");
let (mut write, mut read) = ws_stream.split();
// Spawn a task to handle incoming messages
let read_handle = tokio::spawn(async move {
while let Some(message) = read.next().await {
match message {
Ok(msg) => {
if msg.is_text() {
let text = msg.to_text().unwrap();
println!("\nServer: {}", text);
// Try to parse as structured message
if let Ok(parsed) = serde_json::from_str::<ServerMessage>(text) {
match parsed.message_type.as_str() {
"welcome" => {
if let Some(player_id) = parsed.player_id {
println!("Welcome! Your player ID: {}", player_id);
}
}
"match_found" => {
if let (Some(opponent), Some(color), Some(match_id)) =
(parsed.opponent, parsed.color, parsed.match_id)
{
println!(
"Match found! Opponent: {}, Color: {}, Match ID: {}",
opponent, color, match_id
);
}
}
"error" => {
if let Some(reason) = parsed.reason {
println!("Error: {}", reason);
}
}
_ => {}
}
}
}
}
Err(e) => {
eprintln!("Error receiving message: {}", e);
break;
}
}
}
});
// Main loop for sending messages
println!("\nAvailable commands:");
println!(" join <username> - Join the server");
println!(" findmatch - Find a match");
println!(" move <from> <to> - Make a move (e.g., move e2 e4)");
println!(" chat <message> - Send chat message");
println!(" resign - Resign from current game");
println!(" quit - Exit client");
println!();
loop {
print!("➡️ Enter command: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
continue;
}
let parts: Vec<&str> = input.split_whitespace().collect();
let command = parts[0].to_lowercase();
match command.as_str() {
"quit" | "exit" => {
println!("👋 Goodbye!");
break;
}
"join" => {
if parts.len() >= 2 {
let username = parts[1..].join(" ");
let message = ClientMessage::Join { username };
send_message(&mut write, &message).await?;
} else {
println!("Usage: join <username>");
}
}
"findmatch" | "find" => {
let message = ClientMessage::FindMatch;
send_message(&mut write, &message).await?;
println!("🔍 Searching for a match...");
}
"move" => {
if parts.len() >= 3 {
let from = parts[1].to_string();
let to = parts[2].to_string();
let message = ClientMessage::Move { from, to };
send_message(&mut write, &message).await?;
println!("♟️ Sent move: {} -> {}", parts[1], parts[2]);
} else {
println!("Usage: move <from> <to> (e.g., move e2 e4)");
}
}
"chat" => {
if parts.len() >= 2 {
let text = parts[1..].join(" ");
let message = ClientMessage::Chat { text };
send_message(&mut write, &message).await?;
} else {
println!("Usage: chat <message>");
}
}
"resign" => {
let message = ClientMessage::Resign;
send_message(&mut write, &message).await?;
println!("Resigned from current game");
}
"help" => {
print_help();
}
_ => {
println!(
"Unknown command: {}. Type 'help' for available commands.",
command
);
}
}
}
// Cleanup
read_handle.abort();
Ok(())
}
async fn send_message(
write: &mut futures_util::stream::SplitSink<
tokio_tungstenite::WebSocketStream<
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
>,
Message,
>,
message: &ClientMessage,
) -> Result<(), Box<dyn std::error::Error>> {
let json = serde_json::to_string(message)?;
write.send(Message::Text(json)).await?;
Ok(())
}
fn print_help() {
println!("\n📖 Available Commands:");
println!(" join <username> - Register with a username");
println!(" findmatch - Enter matchmaking queue");
println!(" move <from> <to> - Make a chess move");
println!(" chat <message> - Send chat to opponent");
println!(" resign - Resign from current game");
println!(" help - Show this help");
println!(" quit - Exit the client");
println!();
}

218
server/src/connection.rs Normal file
View File

@@ -0,0 +1,218 @@
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
use uuid::Uuid;
// Type definitions
pub type Tx = futures_util::stream::SplitSink<WebSocketStream<TcpStream>, Message>;
pub type ConnectionMap = Arc<Mutex<HashMap<Uuid, PlayerConnection>>>;
pub type MatchMap = Arc<Mutex<HashMap<Uuid, GameMatch>>>;
pub type WaitingQueue = Arc<Mutex<VecDeque<Uuid>>>;
// Helper functions to create new instances
pub fn new_connection_map() -> ConnectionMap {
Arc::new(Mutex::new(HashMap::new()))
}
pub fn new_match_map() -> MatchMap {
Arc::new(Mutex::new(HashMap::new()))
}
pub fn new_waiting_queue() -> WaitingQueue {
Arc::new(Mutex::new(VecDeque::new()))
}
#[derive(Debug)]
pub struct PlayerConnection {
pub id: Uuid,
pub username: Option<String>,
pub tx: Tx,
pub current_match: Option<Uuid>,
}
#[derive(Debug, Clone)]
pub struct GameMatch {
pub id: Uuid,
pub player_white: Uuid,
pub player_black: Uuid,
pub board_state: String,
pub move_history: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Step {
pub from: String,
pub to: String,
}
// Message sending utilities
pub async fn send_message_to_player(
connections: &ConnectionMap,
player_id: Uuid,
message: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut connections_lock = connections.lock().await;
if let Some(connection) = connections_lock.get_mut(&player_id) {
connection
.tx
.send(Message::Text(message.to_string()))
.await?;
}
Ok(())
}
pub async fn broadcast_to_all(connections: &ConnectionMap, message: &str) {
let mut connections_lock = connections.lock().await;
let mut dead_connections = Vec::new();
for (id, connection) in connections_lock.iter_mut() {
if let Err(e) = connection.tx.send(Message::Text(message.to_string())).await {
eprintln!("Failed to send to {}: {}", id, e);
dead_connections.push(*id);
}
}
// Clean up dead connections
for dead_id in dead_connections {
connections_lock.remove(&dead_id);
}
}
pub async fn broadcast_to_match(
connections: &ConnectionMap,
matches: &MatchMap,
match_id: Uuid,
message: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let matches_lock = matches.lock().await;
if let Some(game_match) = matches_lock.get(&match_id) {
send_message_to_player(connections, game_match.player_white, message).await?;
send_message_to_player(connections, game_match.player_black, message).await?;
}
Ok(())
}
// Connection handler
pub async fn handle_connection(
stream: TcpStream,
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: crate::events::EventSystem,
) -> anyhow::Result<()> {
use tokio_tungstenite::accept_async;
let ws_stream = accept_async(stream).await?;
let (write, mut read) = ws_stream.split();
let player_id = Uuid::new_v4();
// Store the connection
{
let mut conn_map = connections.lock().await;
conn_map.insert(
player_id,
PlayerConnection {
id: player_id,
username: None,
tx: write,
current_match: None,
},
);
}
println!("New connection: {}", player_id);
// Send welcome message
let _ = send_message_to_player(
&connections,
player_id,
&format!(r#"{{"type": "welcome", "player_id": "{}"}}"#, player_id),
)
.await;
// Message processing loop
while let Some(Ok(message)) = read.next().await {
if message.is_text() {
let text = message.to_text()?;
println!("Received from {}: {}", player_id, text);
// TODO: Parse and handle message with event system
// This will be implemented when we integrate the event system
}
}
// Cleanup on disconnect
cleanup_player(player_id, &connections, &matches, &waiting_queue).await;
println!("Connection {} closed", player_id);
Ok(())
}
async fn cleanup_player(
player_id: Uuid,
connections: &ConnectionMap,
_matches: &MatchMap,
waiting_queue: &WaitingQueue,
) {
// Remove from waiting queue
waiting_queue.lock().await.retain(|&id| id != player_id);
// Remove from connections
connections.lock().await.remove(&player_id);
println!("Cleaned up player {}", player_id);
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[tokio::test]
async fn test_send_message_to_nonexistent_player() {
let connections = new_connection_map();
let player_id = Uuid::new_v4();
let result = send_message_to_player(&connections, player_id, "test message").await;
assert!(result.is_ok(), "Should handle missing player gracefully");
}
#[tokio::test]
async fn test_broadcast_to_empty_connections() {
let connections = new_connection_map();
broadcast_to_all(&connections, "test broadcast").await;
let conn_map = connections.lock().await;
assert!(conn_map.is_empty(), "Connections should still be empty");
}
#[tokio::test]
async fn test_connection_cleanup() {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let player_id = Uuid::new_v4();
{
waiting_queue.lock().await.push_back(player_id);
assert_eq!(waiting_queue.lock().await.len(), 1);
}
cleanup_player(player_id, &connections, &matches, &waiting_queue).await;
{
let queue = waiting_queue.lock().await;
assert!(
!queue.contains(&player_id),
"Player should be removed from waiting queue"
);
}
}
}

126
server/src/events.rs Normal file
View File

@@ -0,0 +1,126 @@
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::mpsc;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Debug)]
pub struct Step {
pub from: String,
pub to: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum ClientEvent {
Join { username: String },
FindMatch,
Move { from: String, to: String },
Resign,
Chat { text: String },
RequestLegalMoves { fen: String },
}
#[derive(Debug)]
pub enum ServerEvent {
PlayerJoined(Uuid, String),
PlayerLeft(Uuid),
PlayerJoinedQueue(Uuid),
PlayerJoinedMatch(Uuid, Uuid), // player_id, match_id
PlayerMove(Uuid, Step),
PlayerResigned(Uuid),
MatchCreated(Uuid, Uuid, Uuid), // match_id, white_id, black_id
}
pub struct EventSystem {
sender: mpsc::UnboundedSender<(Uuid, ClientEvent)>,
receiver: Arc<Mutex<mpsc::UnboundedReceiver<(Uuid, ClientEvent)>>>,
}
impl Clone for EventSystem {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
receiver: Arc::clone(&self.receiver),
}
}
}
impl EventSystem {
pub fn new() -> Self {
let (sender, receiver) = mpsc::unbounded_channel();
Self {
sender,
receiver: Arc::new(Mutex::new(receiver)),
}
}
pub async fn send_event(
&self,
player_id: Uuid,
event: ClientEvent,
) -> Result<(), Box<dyn std::error::Error>> {
self.sender.send((player_id, event))?;
Ok(())
}
pub async fn next_event(&self) -> Option<(Uuid, ClientEvent)> {
let mut receiver = self.receiver.lock().await;
receiver.recv().await
}
}
impl Default for EventSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[tokio::test]
async fn test_event_system_send_and_receive() {
let event_system = EventSystem::new();
let player_id = Uuid::new_v4();
let join_event = ClientEvent::Join {
username: "test_user".to_string(),
};
let send_result = event_system.send_event(player_id, join_event).await;
assert!(send_result.is_ok(), "Should send event successfully");
let received = event_system.next_event().await;
assert!(received.is_some(), "Should receive sent event");
let (received_id, received_event) = received.unwrap();
assert_eq!(received_id, player_id, "Should receive correct player ID");
match received_event {
ClientEvent::Join { username } => {
assert_eq!(username, "test_user", "Should receive correct username");
}
_ => panic!("Should receive Join event"),
}
}
#[tokio::test]
async fn test_event_system_clone() {
let event_system1 = EventSystem::new();
let event_system2 = event_system1.clone();
let player_id = Uuid::new_v4();
let event = ClientEvent::FindMatch;
event_system1.send_event(player_id, event).await.unwrap();
let received = event_system2.next_event().await;
assert!(
received.is_some(),
"Cloned event system should receive events"
);
}
}

View File

@@ -1,3 +1,54 @@
fn main() {
println!("Hello, world!");
mod connection;
mod events;
mod matchmaking;
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let address = "0.0.0.0:9001";
let listener = TcpListener::bind(address).await?;
println!("Server running on ws://{}", address);
// Shared state initialization using the new helper functions
let connections = connection::new_connection_map();
let matches = connection::new_match_map();
let waiting_queue = connection::new_waiting_queue();
// Event system for communication between components
let event_system = events::EventSystem::new();
// Start matchmaking background task
let matchmaker = matchmaking::MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
tokio::spawn(async move {
matchmaker.run().await;
});
// Main connection loop
while let Ok((stream, _)) = listener.accept().await {
let connections = connections.clone();
let matches = matches.clone();
let waiting_queue = waiting_queue.clone();
let event_system = event_system.clone();
tokio::spawn(async move {
if let Err(e) = connection::handle_connection(
stream,
connections,
matches,
waiting_queue,
event_system,
)
.await
{
eprintln!("Connection error: {}", e);
}
});
}
Ok(())
}

202
server/src/matchmaking.rs Normal file
View File

@@ -0,0 +1,202 @@
use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue};
use crate::events::EventSystem;
use rand::random;
use uuid::Uuid;
pub struct MatchmakingSystem {
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: EventSystem,
}
impl MatchmakingSystem {
pub fn new(
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: EventSystem,
) -> Self {
Self {
connections,
matches,
waiting_queue,
event_system,
}
}
pub async fn run(&self) {
loop {
self.try_create_match().await;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
async fn try_create_match(&self) {
let mut queue = self.waiting_queue.lock().await;
while queue.len() >= 2 {
let player1 = queue.pop_front().unwrap();
let player2 = queue.pop_front().unwrap();
let match_id = Uuid::new_v4();
let (white_player, black_player) = if random::<bool>() {
(player1, player2)
} else {
(player2, player1)
};
let game_match = GameMatch {
id: match_id,
player_white: white_player,
player_black: black_player,
board_state: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string(),
move_history: Vec::new(),
};
// Store the match
self.matches.lock().await.insert(match_id, game_match);
// Update player connections
{
let mut conn_map = self.connections.lock().await;
if let Some(player) = conn_map.get_mut(&white_player) {
player.current_match = Some(match_id);
}
if let Some(player) = conn_map.get_mut(&black_player) {
player.current_match = Some(match_id);
}
}
// Notify players
self.notify_players(white_player, black_player, match_id)
.await;
}
}
async fn notify_players(&self, white: Uuid, black: Uuid, match_id: Uuid) {
let conn_map = self.connections.lock().await;
// Get opponent names
let white_name = conn_map
.get(&black)
.and_then(|c| c.username.as_deref())
.unwrap_or("Opponent");
let black_name = conn_map
.get(&white)
.and_then(|c| c.username.as_deref())
.unwrap_or("Opponent");
// Notify white player
if let Some(_) = conn_map.get(&white) {
let message = format!(
r#"{{"type": "match_found", "match_id": "{}", "opponent": "{}", "color": "white"}}"#,
match_id, black_name
);
let _ =
crate::connection::send_message_to_player(&self.connections, white, &message).await;
}
// Notify black player
if let Some(_) = conn_map.get(&black) {
let message = format!(
r#"{{"type": "match_found", "match_id": "{}", "opponent": "{}", "color": "black"}}"#,
match_id, white_name
);
let _ =
crate::connection::send_message_to_player(&self.connections, black, &message).await;
}
println!("Match created: {} (white) vs {} (black)", white, black);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::EventSystem;
use uuid::Uuid;
use crate::connection::new_connection_map;
use crate::connection::new_match_map;
use crate::connection::new_waiting_queue;
#[tokio::test]
async fn test_matchmaking_creates_matches() {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let event_system = EventSystem::new();
let matchmaking = MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
let player1 = Uuid::new_v4();
let player2 = Uuid::new_v4();
{
waiting_queue.lock().await.push_back(player1);
waiting_queue.lock().await.push_back(player2);
}
matchmaking.try_create_match().await;
{
let matches_map = matches.lock().await;
assert_eq!(matches_map.len(), 1, "Should create one match");
let game_match = matches_map.values().next().unwrap();
assert!(game_match.player_white == player1 || game_match.player_white == player2);
assert!(game_match.player_black == player1 || game_match.player_black == player2);
assert_ne!(
game_match.player_white, game_match.player_black,
"Players should be different"
);
}
{
let queue = waiting_queue.lock().await;
assert!(
queue.is_empty(),
"Waiting queue should be empty after matchmaking"
);
}
}
#[tokio::test]
async fn test_matchmaking_with_odd_players() {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let event_system = EventSystem::new();
let matchmaking = MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
let player1 = Uuid::new_v4();
{
waiting_queue.lock().await.push_back(player1);
}
matchmaking.try_create_match().await;
{
let matches_map = matches.lock().await;
assert!(
matches_map.is_empty(),
"Should not create match with only one player"
);
let queue = waiting_queue.lock().await;
assert_eq!(queue.len(), 1, "Should keep single player in queue");
}
}
}

36
server/src/messages.rs Normal file
View File

@@ -0,0 +1,36 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum ServerMessage {
Welcome {
player_id: String,
},
MatchFound {
match_id: String,
opponent: String,
color: String,
},
GameStart {
fen: String,
white_time: u32,
black_time: u32,
},
MoveResult {
valid: bool,
from: String,
to: String,
new_fen: String,
},
OpponentMove {
from: String,
to: String,
},
GameEnd {
result: String,
reason: String,
},
Error {
reason: String,
},
}