Compare commits

..

69 Commits

Author SHA1 Message Date
Bence
456d386551 Created a new branch due to a bug
Previous pull request didn't work (didnt change ui/src/main.rs), created new branch to see if the problem persists
2025-11-15 19:06:25 +01:00
Hatvani Tamás
e663d61b9e Merge pull request #11 from htamas1210/Engine/pseudo-moves
Engine/pseudo moves
2025-11-15 18:05:17 +01:00
Hatvani Tamás
c3ed181b07 Merge pull request #10 from htamas1210/Engine/legality-checks
Engine/legality checks
2025-11-15 15:03:38 +01:00
Hatvani Tamás
5b3f2488d4 Merge pull request #9 from htamas1210/Engine/board-repr
Engine/board repr
2025-11-15 14:53:07 +01:00
Varga Dávid Lajos
659413ca31 implemented pseudo-move generation for queens 2025-11-15 13:47:46 +01:00
Varga Dávid Lajos
4de505bc21 implemented pseudo-move generation for rooks 2025-11-15 13:47:03 +01:00
Varga Dávid Lajos
7c58f0d508 implemented pseudo-move generation for bishops 2025-11-15 13:46:11 +01:00
Varga Dávid Lajos
c9246d1342 added helper function: get_raycast_from_square_in_direction 2025-11-15 13:45:01 +01:00
Varga Dávid Lajos
eab795b6df implemented pseudo-move generation for pawn captures 2025-11-15 13:43:37 +01:00
Varga Dávid Lajos
f8ab14a026 implemented pseudo-move generation for kings 2025-11-15 13:42:41 +01:00
Varga Dávid Lajos
274897a070 implemented pseudo-move generation for knights 2025-11-15 13:41:40 +01:00
Varga Dávid Lajos
8ecbeb9c41 implemented pseudo-move generation for pawn moves 2025-11-15 13:40:40 +01:00
Varga Dávid Lajos
a1482d11f2 added file and module structure for bitboard::attacks.rs 2025-11-15 13:38:48 +01:00
Varga Dávid Lajos
f9a302c9a0 implemented method add_checker for CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:40:52 +01:00
Varga Dávid Lajos
ad530e9155 added constructor for struct CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:39:35 +01:00
Varga Dávid Lajos
182aa59ee1 defined shape of struct CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:37:47 +01:00
Varga Dávid Lajos
296f1f6c3a added file and module structure for checkinfo.rs 2025-11-15 12:36:11 +01:00
Varga Dávid Lajos
f7355f8e74 implemented use of calc_pinned_squares in board.rs 2025-11-15 12:33:18 +01:00
Varga Dávid Lajos
cd58a7a321 added methods for detecting pinned pieces 2025-11-15 12:30:01 +01:00
Varga Dávid Lajos
2beb8ab303 added file and module structure for bitboard::legality.rs 2025-11-15 10:31:02 +01:00
Varga Dávid Lajos
5425ccc5cd added partial constructor for starting from a fen position 2025-11-15 10:26:31 +01:00
Varga Dávid Lajos
1104c8e6c5 added helper method for fen parsing to utils.rs 2025-11-15 10:21:00 +01:00
Varga Dávid Lajos
274ffcf5ec added method to get the current square of a king 2025-11-15 10:14:37 +01:00
Varga Dávid Lajos
5854dbc20b set each field to pub(in super) for later use 2025-11-15 10:12:52 +01:00
Varga Dávid Lajos
f36a196b2f added getters for fields 2025-11-15 10:11:34 +01:00
Varga Dávid Lajos
11f26809df used calc_occupancy and calc_piece_board methods in the initial state constructor 2025-11-15 10:07:23 +01:00
Varga Dávid Lajos
c88fbe68e3 added and used necessary methods the initial state constructor 2025-11-15 10:06:12 +01:00
Varga Dávid Lajos
7f4c53ddb7 added partial constructor for initial board state to board.rs 2025-11-15 10:03:24 +01:00
Varga Dávid Lajos
38b38845d6 added constructor for clear board to board.rs 2025-11-15 09:58:38 +01:00
Varga Dávid Lajos
66dd2877b2 defined board representation shape in board.rs 2025-11-15 09:56:17 +01:00
Varga Dávid Lajos
3cd53c7d70 added file and module structure for board representation 2025-11-15 09:51:07 +01:00
Bence
5b3bdc750f Merge pull request #8 from htamas1210/Server/websocket
Server/websocket
2025-11-13 19:12:00 +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
Hatvani Tamás
eaf3bfa192 Merge pull request #5 from htamas1210/Engine/movegen-lut
Engine/movegen lut
2025-11-11 12:17:48 +01:00
24 changed files with 6268 additions and 24 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,7 @@
mod attackmaps;
mod attackmaps;
mod utils;
mod legality;
mod checkinfo;
mod attacks;
pub mod board;

View File

@@ -0,0 +1,89 @@
use super::board::Board;
use super::attackmaps::*;
impl Board {
const RANK_2: u64 = 0x0000_0000_0000_FF00;
const RANK_7: u64 = 0x00FF_0000_0000_0000;
pub fn get_pseudo_pawn_moves(&self, sq: u32) -> u64 {
let pawn: u64 = 1 << sq;
let mut move_mask: u64 = 0u64;
let move_offset: i8 = 8 - 16 * self.side_to_move as i8;
let next_sq: u64 = if move_offset > 0 {pawn << move_offset} else {pawn >> -move_offset};
if (self.occupancy[2] & next_sq) == 0 {
move_mask |= next_sq;
if (self.side_to_move == 0 && pawn & Self::RANK_2 != 0)
|| (self.side_to_move == 1 && pawn & Self::RANK_7 != 0) {
let next_sq: u64 = if move_offset > 0 {next_sq << move_offset} else {next_sq >> -move_offset};
if (self.occupancy[2] & next_sq) == 0 {
move_mask |= next_sq;
}
}
}
return move_mask;
}
#[inline]
pub fn get_pseudo_knight_moves(&self, sq: u32) -> u64 {
return KNIGHT_ATTACK_MAP[sq as usize];
}
#[inline]
pub fn get_pseudo_king_moves(&self, sq: u32) -> u64 {
return KING_ATTACK_MAP[sq as usize];
}
#[inline]
pub fn get_pseudo_pawn_captures(&self, sq: u32) -> u64 {
return PAWN_ATTACK_MAP[sq as usize][self.side_to_move as usize];
}
#[inline]
pub fn get_pseudo_opponent_pawn_captures(&self, sq: u32) -> u64 {
return PAWN_ATTACK_MAP[sq as usize][1 - self.side_to_move as usize];
}
#[inline]
pub fn get_pseudo_bishop_moves(&self, sq: u32) -> u64 {
let mut moves = 0u64;
let sq = sq as usize;
let occupancy = self.occupancy[2];
moves |= get_raycast_from_square_in_direction(occupancy, sq, 1);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 3);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 5);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 7);
return moves;
}
#[inline]
pub fn get_pseudo_rook_moves(&self, sq: u32) -> u64 {
let mut moves: u64 = 0u64;
let occupancy = self.occupancy[2];
let sq = sq as usize;
moves |= get_raycast_from_square_in_direction(occupancy, sq, 0);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 2);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 4);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 6);
return moves;
}
#[inline(always)]
pub fn get_pseudo_queen_moves(&self, sq: u32) -> u64 {
return self.get_pseudo_bishop_moves(sq) | self.get_pseudo_rook_moves(sq);
}
}
#[inline(always)]
pub fn get_raycast_from_square_in_direction(occupancy: u64, sq: usize, dir: usize) -> u64 {
let is_up: bool = dir / 4 == 0;
let mut ray: u64 = RAY_TABLE[sq][dir];
let blockers: u64 = occupancy & ray;
if blockers != 0 {
let first_blocker: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
ray &= !RAY_TABLE[first_blocker as usize][dir];
}
return ray;
}

View File

@@ -0,0 +1,182 @@
use super::utils::try_get_square_number_from_notation;
pub struct Board {
pub(in super) bitboards: [u64; 12], // 0-5 -> white pieces (P, N, B, R, Q, K), 6-11 -> black pieces (p, n, b, r, q, k)
pub(in super) piece_board: [u8; 64], // same as board indexes, 12 -> empty square
pub(in super) occupancy: [u64; 3], // 0 -> white, 1 -> black, 2 -> combined
pub(in super) castling_rights: u8, // 0b0000_KQkq
pub(in super) pinned_squares: [u8; 64], // 0 -> E-W, 1 -> NE-SW, 2 -> N-S, 3 -> SE-NW, 4 -> no pin
pub(in super) pin_mask: u64, // 1 -> pin, 0 -> no pin
pub(in super) en_passant_square: u64, // 1 -> ep square, 0 -> no ep square
pub(in super) side_to_move: u8 // 0 -> white to play, 1 -> black to play
}
impl Board {
pub fn new_clear() -> Self {
let mut bit_board: Self = Self {
bitboards: [0x0000_0000_0000_0000; 12],
piece_board: [12; 64],
occupancy: [0x0000_0000_0000_0000; 3],
castling_rights: 0b0000_0000,
pinned_squares: [4; 64],
pin_mask: 0u64,
en_passant_square: 0x0000_0000_0000_0000,
side_to_move: 0
};
return bit_board;
}
pub fn new() -> Self {
let mut bit_board: Board = Self {
bitboards: [0x0000_0000_0000_FF00,
0x0000_0000_0000_0042,
0x0000_0000_0000_0024,
0x0000_0000_0000_0081,
0x0000_0000_0000_0008,
0x0000_0000_0000_0010,
0x00FF_0000_0000_0000,
0x4200_0000_0000_0000,
0x2400_0000_0000_0000,
0x8100_0000_0000_0000,
0x0800_0000_0000_0000,
0x1000_0000_0000_0000],
piece_board: [12; 64],
occupancy: [0; 3],
castling_rights: 0b0000_1111,
pinned_squares: [4; 64],
pin_mask: 0u64,
en_passant_square: 0x0000_0000_0000_0000,
side_to_move: 0
};
bit_board.calc_occupancy();
bit_board.calc_piece_board();
return bit_board;
}
pub fn build(fen: &str) -> Self {
let mut board: Board = Board::new_clear();
let mut col: i32 = 0;
let mut row: i32 = 7;
let pieces: [char; 12] = ['p', 'n', 'b', 'r', 'q', 'k', 'P', 'N', 'B', 'R', 'Q', 'K'];
let mut coming_up: &str = fen;
for (i, c) in coming_up.chars().enumerate() {
if pieces.contains(&c) {
// board.place_piece(row*8 + col, c);
col += 1;
}
else if ('1'..='8').contains(&c) {
col += c.to_string().parse::<i32>().unwrap();
}
else if c == '/' {
row -= 1;
col = 0;
}
else {
coming_up = &coming_up[i+1..];
break;
}
}
board.calc_occupancy();
match coming_up.chars().next().unwrap() {
'w' => board.side_to_move = 0,
'b' => board.side_to_move = 1,
_ => panic!("invalid fen notation / to be handled later")
}
coming_up = &coming_up[2..];
for (i, c) in coming_up.chars().enumerate() {
match c {
'K' => board.castling_rights |= 1 << 3,
'Q' => board.castling_rights |= 1 << 2,
'k' => board.castling_rights |= 1 << 1,
'q' => board.castling_rights |= 1,
'-' => {
coming_up = &coming_up[i+2..];
break;
}
_ => {
coming_up = &coming_up[i+1..];
break;
}
}
}
match coming_up.chars().next().unwrap() {
'-' => {
coming_up = &coming_up[1..];
}
_ => {
let notation = coming_up.split(' ').next().unwrap();
if let Ok(epsq_index) = try_get_square_number_from_notation(notation) {
board.en_passant_square = 1 << epsq_index;
}
}
}
board.calc_pinned_squares();
board.calc_piece_board();
return board;
}
#[inline(always)]
pub fn bitboards(&self, index: usize) -> u64 {
return self.bitboards[index];
}
#[inline(always)]
pub fn piece_board(&self, sq: u8) -> u8 {
return self.piece_board[sq as usize];
}
#[inline(always)]
pub fn occupancy(&self, side: usize) -> u64 {
return self.occupancy[side];
}
#[inline(always)]
pub fn castling_rights(&self) -> u8 {
return self.castling_rights;
}
#[inline(always)]
pub fn pinned_squares(&self, sq: usize) -> u8 {
return self.pinned_squares[sq];
}
#[inline(always)]
pub fn pin_mask(&self) -> u64 {
return self.pin_mask;
}
#[inline(always)]
pub fn en_passant_square(&self) -> u64 {
return self.en_passant_square;
}
#[inline(always)]
pub fn side_to_move(&self) -> u8 {
return self.side_to_move;
}
#[inline(always)]
pub fn current_king_square(&self) -> u32 {
return if self.side_to_move == 0 { self.bitboards[5].trailing_zeros() } else { self.bitboards[11].trailing_zeros() };
}
fn calc_occupancy(&mut self) {
self.occupancy = [0u64; 3];
for b in 0..6 {
self.occupancy[0] |= self.bitboards[b];
}
for b in 6..12 {
self.occupancy[1] |= self.bitboards[b];
}
self.occupancy[2] = self.occupancy[0] | self.occupancy[1];
}
fn calc_piece_board(&mut self) {
for sq in 0..64 {
for b in 0..12 {
if (self.bitboards[b as usize] & 1 << sq) != 0 {
self.piece_board[sq] = b;
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
pub struct CheckInfo {
pub check_count: u8,
pub move_mask: u64
}
impl CheckInfo {
pub fn new() -> Self {
return Self {
check_count: 0,
move_mask: 0xFFFF_FFFF_FFFF_FFFF
}
}
#[inline(always)]
pub fn add_checker(&mut self, move_mask: u64) {
self.move_mask &= move_mask;
self.check_count += 1;
}
}

View File

@@ -0,0 +1,48 @@
use super::board::Board;
use super::attackmaps::RAY_TABLE;
impl Board {
pub(in super) fn calc_pinned_squares(&mut self) {
self.pinned_squares = [4; 64];
self.pin_mask = 0u64;
let friendly_pieces: u64 = self.occupancy[self.side_to_move as usize];
let offset: usize = 6 * self.side_to_move as usize;
let king_board: u64 = self.bitboards[5 + offset];
let king_sq: u32 = king_board.trailing_zeros();
let opponent_queen_bishop_mask: u64 = self.bitboards[8 - offset] | self.bitboards[10 - offset];
let opponent_queen_rook_mask: u64 = self.bitboards[9 - offset] | self.bitboards[10 - offset];
// Queen-Rook directions
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 0);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 2);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 4);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 6);
// Queen-Bishop directions
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 1);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 3);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 5);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 7);
}
pub(in super) fn set_pinned_in_ray_direction(&mut self, king_sq: u32, friendly_pieces: u64, attackers: u64, dir: u8) {
let is_up: bool = dir / 4 == 0;
let mask: u64 = RAY_TABLE[king_sq as usize][dir as usize];
let blockers: u64 = self.occupancy[2] & mask;
if blockers == 0 { return; }
let first_blocker_sq: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
if (friendly_pieces & 1 << first_blocker_sq) != 0 {
let blockers: u64 = blockers & !(1 << first_blocker_sq);
if blockers == 0 { return; }
let second_blocker_sq: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
if (attackers & 1 << second_blocker_sq) != 0 {
self.pinned_squares[first_blocker_sq as usize] = dir % 4;
self.pin_mask |= 1 << first_blocker_sq;
}
}
}
}

View File

@@ -0,0 +1,120 @@
#[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;
}
pub fn try_get_square_number_from_notation(notation: &str) -> Result<u8, ()> {
let file = match notation.chars().nth(0).unwrap() {
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 3,
'e' => 4,
'f' => 5,
'g' => 6,
'h' => 7,
_ => { return Result::Err(()); }
};
if let Some(rank) = notation.chars().nth(1) {
return Result::Ok(file + 8 * (rank.to_digit(10).unwrap() as u8) - 8);
}
else {
return Result::Err(());
}
}
// <----- 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,13 @@ 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"

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,
},
}

4371
ui/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
eframe = "0.33.0"
egui = "0.33.0"
tokio-tungstenite = "0.28.0"
winit = "0.30.12"

BIN
ui/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

View File

@@ -1,3 +1,430 @@
fn main() {
println!("Hello, world!");
use eframe::egui;
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions{
viewport: egui::ViewportBuilder::default()
.with_fullscreen(false)
.with_min_inner_size(egui::vec2(800.0, 600.0)) // Minimum width, height
.with_inner_size(egui::vec2(7680.0, 4320.0)), // Initial size
..Default::default()
};
eframe::run_native(
"Knightly",
options,
Box::new(|cc| {
let mut fonts = egui::FontDefinitions::default();
fonts.font_data.insert(
"symbols".to_owned(),
egui::FontData::from_static(include_bytes!("../fonts/DejaVuSans.ttf")).into(),
);
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "symbols".to_owned());
cc.egui_ctx.set_fonts(fonts);
Ok(Box::new(ChessApp::default()))
}),
)
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum Piece {
King(char),
Queen(char),
Rook(char),
Bishop(char),
Knight(char),
Pawn(char),
Empty,
}
impl Piece {
fn symbol(&self) -> &'static str {
match self {
Piece::King('w') => "",
Piece::Queen('w') => "",
Piece::Rook('w') => "",
Piece::Bishop('w') => "",
Piece::Knight('w') => "",
Piece::Pawn('w') => "",
Piece::King('b') => "",
Piece::Queen('b') => "",
Piece::Rook('b') => "",
Piece::Bishop('b') => "",
Piece::Knight('b') => "",
Piece::Pawn('b') => "♟︎",
Piece::Empty => "",
_ => "",
}
}
}
#[derive(PartialEq, Debug)]
enum Turn {
White,
Black,
}
enum AppState {
MainMenu,
InGame,
Settings,
}
struct ChessApp {
fullscreen: bool,
resolutions: Vec<(u32, u32)>,
selected_resolution: usize,
state: AppState,
board: [[Piece; 8]; 8],
selected: Option<(usize, usize)>,
turn: Turn,
pending_settings: PendingSettings,
server_port: String,
}
#[derive(Default)]
struct PendingSettings {
fullscreen: bool,
selected_resolution: usize,
server_port: String,
}
impl Default for ChessApp {
fn default() -> Self {
Self {
fullscreen: true,
resolutions: vec![
(1280, 720),
(1600, 900),
(1920, 1080),
(2560, 1440),
(3840, 2160),
],
selected_resolution: 2, // Default to 1920x1080
state: AppState::MainMenu,
board: Self::starting_board(),
selected: None,
turn: Turn::White,
pending_settings: PendingSettings::default(),
server_port: "8080".to_string(), // Default port
}
}
}
impl ChessApp {
fn starting_board() -> [[Piece; 8]; 8] {
use Piece::*;
[
[
Rook('b'),
Knight('b'),
Bishop('b'),
Queen('b'),
King('b'),
Bishop('b'),
Knight('b'),
Rook('b'),
],
[Pawn('b'); 8],
[Empty; 8],
[Empty; 8],
[Empty; 8],
[Empty; 8],
[Pawn('w'); 8],
[
Rook('w'),
Knight('w'),
Bishop('w'),
Queen('w'),
King('w'),
Bishop('w'),
Knight('w'),
Rook('w'),
],
]
}
fn handle_click(&mut self, row: usize, col: usize) {
if let Some((r, c)) = self.selected {
let piece = self.board[r][c];
self.board[r][c] = Piece::Empty;
self.board[row][col] = piece;
self.selected = None;
self.turn = if self.turn == Turn::White {
Turn::Black
} else {
Turn::White
};
} else {
if self.board[row][col] != Piece::Empty {
self.selected = Some((row, col));
}
}
}
fn apply_settings(&mut self, ctx: &egui::Context) {
self.fullscreen = self.pending_settings.fullscreen;
self.selected_resolution = self.pending_settings.selected_resolution;
self.server_port = self.pending_settings.server_port.clone();
if let Some(resolution) = self.resolutions.get(self.selected_resolution) {
ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(
egui::Vec2::new(resolution.0 as f32, resolution.1 as f32)
));
}
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(self.fullscreen));
}
fn enter_settings(&mut self) {
self.pending_settings.fullscreen = self.fullscreen;
self.pending_settings.selected_resolution = self.selected_resolution;
self.pending_settings.server_port = self.server_port.clone();
self.state = AppState::Settings;
}
}
impl eframe::App for ChessApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
match self.state {
AppState::MainMenu => {
egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.heading("♞ Knightly ♞");
ui.add_space(30.0);
if ui.add_sized([300.0, 60.0], egui::Button::new("Play")).clicked() {
self.state = AppState::InGame;
}
ui.add_space(8.0);
if ui.add_sized([300.0, 60.0], egui::Button::new("Settings")).clicked() {
self.enter_settings();
}
ui.add_space(8.0);
if ui
.add_sized([300.0, 60.0], egui::Button::new("Quit"))
.clicked()
{
std::process::exit(0);
}
});
});
}
AppState::Settings => {
egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.heading("Settings");
ui.add_space(30.0);
// Fullscreen toggle
ui.horizontal(|ui| {
ui.label("Fullscreen:");
if ui.checkbox(&mut self.pending_settings.fullscreen, "").changed() {
// If enabling fullscreen, we might want to disable resolution selection
}
});
ui.add_space(10.0);
// Resolution dropdown
ui.horizontal(|ui| {
ui.label("Resolution:");
egui::ComboBox::new("resolution_combo", "")
.selected_text(format!(
"{}x{}",
self.resolutions[self.pending_settings.selected_resolution].0,
self.resolutions[self.pending_settings.selected_resolution].1
))
.show_ui(ui, |ui| {
for (i, &(width, height)) in self.resolutions.iter().enumerate() {
ui.selectable_value(
&mut self.pending_settings.selected_resolution,
i,
format!("{}x{}", width, height),
);
}
});
});
ui.add_space(10.0);
// Server port input field
ui.horizontal(|ui| {
ui.label("Local Server Port:");
ui.add(egui::TextEdit::singleline(&mut self.pending_settings.server_port)
.desired_width(100.0)
.hint_text("8080"));
});
ui.add_space(30.0);
// Apply and Cancel buttons
ui.horizontal(|ui| {
if ui.add_sized([140.0, 40.0], egui::Button::new("Apply")).clicked() {
self.apply_settings(ctx);
self.state = AppState::MainMenu;
}
if ui.add_sized([140.0, 40.0], egui::Button::new("Cancel")).clicked() {
self.state = AppState::MainMenu;
}
});
});
});
}
AppState::InGame => {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
ui.horizontal(|ui| {
if ui.button("Main Menu").clicked() {
self.state = AppState::MainMenu;
}
if ui.button("Settings").clicked() {
self.enter_settings();
}
if ui.button("New Game").clicked() {
*self = ChessApp::default();
self.state = AppState::InGame;
}
ui.separator();
ui.label(format!("Turn: {:?}", self.turn));
});
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
let full_avail = ui.available_rect_before_wrap();
let board_tile = (full_avail.width().min(full_avail.height())) / 8.0;
let board_size = board_tile * 8.0;
// Create a child UI at the board position
let (response, painter) = ui.allocate_painter(
egui::Vec2::new(board_size, board_size),
egui::Sense::click()
);
let board_rect = egui::Rect::from_center_size(
full_avail.center(),
egui::vec2(board_size, board_size)
);
// Draw the chess board
let tile_size = board_size / 8.0;
for row in 0..8 {
for col in 0..8 {
let color = if (row + col) % 2 == 0 {
egui::Color32::from_rgb(100, 97, 97)
} else {
egui::Color32::from_rgb(217, 217, 217)
};
let rect = egui::Rect::from_min_size(
egui::Pos2::new(
board_rect.min.x + col as f32 * tile_size,
board_rect.min.y + row as f32 * tile_size
),
egui::Vec2::new(tile_size, tile_size)
);
painter.rect_filled(rect, 0.0, color);
// Draw piece
let piece = self.board[row][col];
if piece != Piece::Empty {
let symbol = piece.symbol();
let font_id = egui::FontId::proportional(tile_size * 0.75);
painter.text(
rect.center(),
egui::Align2::CENTER_CENTER,
symbol,
font_id,
if matches!(piece, Piece::King('w') | Piece::Queen('w') | Piece::Rook('w') | Piece::Bishop('w') | Piece::Knight('w') | Piece::Pawn('w')) {
egui::Color32::WHITE
} else {
egui::Color32::BLACK
}
);
}
// Draw selection highlight
if self.selected == Some((row, col)) {
painter.rect_stroke(
rect,
0.0,
egui::Stroke::new(3.0, egui::Color32::RED),
egui::StrokeKind::Inside
);
}
// Handle clicks
if ui.ctx().input(|i| i.pointer.primary_clicked()) {
let click_pos = ui.ctx().input(|i| i.pointer.interact_pos()).unwrap();
if rect.contains(click_pos) {
self.handle_click(row, col);
}
}
}
}
});
});
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_board_setup() {
let app = ChessApp::default();
assert!(matches!(app.board[0][0], Piece::Rook('b')));
assert!(matches!(app.board[7][0], Piece::Rook('w')));
assert!(matches!(app.board[1][0], Piece::Pawn('b')));
assert!(matches!(app.board[6][0], Piece::Pawn('w')));
}
#[test]
fn test_piece_symbols() {
assert_eq!(Piece::King('w').symbol(), "");
assert_eq!(Piece::King('b').symbol(), "");
assert_eq!(Piece::Empty.symbol(), "");
}
#[test]
fn test_piece_selection() {
let mut app = ChessApp::default();
app.handle_click(6, 0);
assert_eq!(app.selected, Some((6, 0)));
app.handle_click(6, 0);
assert_eq!(app.selected, None);
}
#[test]
fn test_piece_movement() {
let mut app = ChessApp::default();
// Select and move a piece
app.handle_click(6, 0); // Select white pawn
app.handle_click(5, 0); // Move to empty square
assert_eq!(app.board[6][0], Piece::Empty);
assert!(matches!(app.board[5][0], Piece::Pawn('w')));
}
#[test]
fn test_turn_switching() {
let mut app = ChessApp::default();
assert_eq!(app.turn, Turn::White);
app.handle_click(6, 0); // White selects
app.handle_click(5, 0); // White moves
assert_eq!(app.turn, Turn::Black); // Should now be Black's turn
}
#[test]
fn test_server_port_default() {
let app = ChessApp::default();
assert_eq!(app.server_port, "8080");
}
}