new file: .DS_Store

new file:   CTF/.ipynb_checkpoints/fixed_map_example-checkpoint.png
	new file:   CTF/.ipynb_checkpoints/random_map_example-checkpoint.png
	new file:   CTF/CTF/README.md
	new file:   CTF/CTF/backend/example_init.json
	new file:   CTF/CTF/backend/example_plan_next_actions.json
	new file:   CTF/CTF/backend/lib/game_engine.py
	new file:   CTF/CTF/backend/main.ipynb
	new file:   CTF/CTF/backend/pick_closest_flag.py
	new file:   CTF/CTF/backend/pick_flag_ai.py
	new file:   CTF/CTF/backend/python_tutorial.ipynb
	new file:   CTF/CTF/backend/start.sh
	new file:   CTF/CTF/backend/test_lib.ipynb
	new file:   CTF/CTF/fixed_map_example.png
	new file:   CTF/CTF/frontend/assets/characters.png
	new file:   CTF/CTF/frontend/assets/characters_red_flag.png
	new file:   CTF/CTF/frontend/assets/characters_yellow_flag.png
	new file:   CTF/CTF/frontend/assets/red_flag_16_16.png
	new file:   CTF/CTF/frontend/assets/red_flag_32_32.png
	new file:   CTF/CTF/frontend/assets/tilemap.json
	new file:   CTF/CTF/frontend/assets/tilemap.tmx
	new file:   CTF/CTF/frontend/assets/tiles.png
	new file:   CTF/CTF/frontend/assets/yellow_flag_16_16.png
	new file:   CTF/CTF/frontend/assets/yellow_flag_32_32.png
	new file:   CTF/CTF/frontend/game_config.json
	new file:   CTF/CTF/frontend/index.html
	new file:   CTF/CTF/frontend/phaser.js
	new file:   CTF/CTF/frontend/project.config
	new file:   CTF/CTF/frontend/src/assets.js
	new file:   CTF/CTF/frontend/src/gameObjects/Flag.js
	new file:   CTF/CTF/frontend/src/gameObjects/Player.js
	new file:   CTF/CTF/frontend/src/main.js
	new file:   CTF/CTF/frontend/src/scenes/Boot.js
	new file:   CTF/CTF/frontend/src/scenes/Game.js
	new file:   CTF/CTF/frontend/src/scenes/GameOver.js
	new file:   CTF/CTF/frontend/src/scenes/Preloader.js
	new file:   CTF/CTF/frontend/start.sh
	new file:   CTF/CTF/random_map_example.png
	new file:   CTF/README.md
	new file:   CTF/backend/.ipynb_checkpoints/main-checkpoint.ipynb
	new file:   CTF/backend/.ipynb_checkpoints/pick_closest_flag-checkpoint.py
	new file:   CTF/backend/.ipynb_checkpoints/pick_flag_ai-checkpoint.py
	new file:   CTF/backend/.ipynb_checkpoints/python_tutorial-checkpoint.ipynb
	new file:   CTF/backend/.ipynb_checkpoints/start-checkpoint.sh
	new file:   CTF/backend/.ipynb_checkpoints/test_lib-checkpoint.ipynb
	new file:   CTF/backend/example_init.json
	new file:   CTF/backend/example_plan_next_actions.json
	new file:   CTF/backend/lib/__pycache__/game_engine.cpython-313.pyc
	new file:   CTF/backend/lib/game_engine.py
	new file:   CTF/backend/main.ipynb
	new file:   CTF/backend/pick_closest_flag.py
	new file:   CTF/backend/pick_flag_ai.py
	new file:   CTF/backend/python_tutorial.ipynb
	new file:   CTF/backend/start.sh
	new file:   CTF/backend/test_lib.ipynb
	new file:   CTF/fixed_map_example.png
	new file:   CTF/frontend/.ipynb_checkpoints/game_config-checkpoint.json
	new file:   CTF/frontend/assets/.ipynb_checkpoints/tilemap-checkpoint.json
	new file:   CTF/frontend/assets/characters.png
	new file:   CTF/frontend/assets/characters_red_flag.png
	new file:   CTF/frontend/assets/characters_yellow_flag.png
	new file:   CTF/frontend/assets/red_flag_16_16.png
	new file:   CTF/frontend/assets/red_flag_32_32.png
	new file:   CTF/frontend/assets/tilemap.json
	new file:   CTF/frontend/assets/tilemap.tmx
	new file:   CTF/frontend/assets/tiles.png
	new file:   CTF/frontend/assets/yellow_flag_16_16.png
	new file:   CTF/frontend/assets/yellow_flag_32_32.png
	new file:   CTF/frontend/game_config.json
	new file:   CTF/frontend/index.html
	new file:   CTF/frontend/phaser.js
	new file:   CTF/frontend/project.config
	new file:   CTF/frontend/src/assets.js
	new file:   CTF/frontend/src/gameObjects/Flag.js
	new file:   CTF/frontend/src/gameObjects/Player.js
	new file:   CTF/frontend/src/main.js
	new file:   CTF/frontend/src/scenes/Boot.js
	new file:   CTF/frontend/src/scenes/Game.js
	new file:   CTF/frontend/src/scenes/GameOver.js
	new file:   CTF/frontend/src/scenes/Preloader.js
	new file:   CTF/frontend/start.sh
	new file:   CTF/random_map_example.png
This commit is contained in:
2025-12-27 15:59:21 +08:00
parent 2f845586a2
commit d27e376e11
82 changed files with 516075 additions and 0 deletions
@@ -0,0 +1,213 @@
{
"teams": [
{ "name": "L", "who": "user11-1"},
{ "name": "R", "who": "human"}
],
"setup": {
"numPlayers": 1,
"numFlags": 3,
"useRandomFlags": false
},
"servers": {
"user0-1": "ws://115.191.4.103:34568",
"user0-2": "ws://115.191.4.103:34569",
"user1-1": "ws://115.191.4.103:34571",
"user1-2": "ws://115.191.4.103:34572",
"user2-1": "ws://115.191.4.103:34574",
"user2-2": "ws://115.191.4.103:34575",
"user3-1": "ws://115.191.4.103:34577",
"user3-2": "ws://115.191.4.103:34578",
"user4-1": "ws://115.191.4.103:34580",
"user4-2": "ws://115.191.4.103:34581",
"user5-1": "ws://115.191.4.103:34583",
"user5-2": "ws://115.191.4.103:34584",
"user6-1": "ws://115.191.4.103:34586",
"user6-2": "ws://115.191.4.103:34587",
"user7-1": "ws://115.191.4.103:34589",
"user7-2": "ws://115.191.4.103:34590",
"user8-1": "ws://115.191.4.103:34592",
"user8-2": "ws://115.191.4.103:34593",
"user9-1": "ws://115.191.4.103:34595",
"user9-2": "ws://115.191.4.103:34596",
"user10-1": "ws://115.191.4.103:34598",
"user10-2": "ws://115.191.4.103:34599",
"user11-1": "ws://115.191.4.103:34601",
"user11-2": "ws://115.191.4.103:34602",
"user12-1": "ws://115.191.4.103:34604",
"user12-2": "ws://115.191.4.103:34605",
"user13-1": "ws://115.191.4.103:34607",
"user13-2": "ws://115.191.4.103:34608",
"user14-1": "ws://115.191.4.103:34610",
"user14-2": "ws://115.191.4.103:34611",
"user15-1": "ws://115.191.4.103:34613",
"user15-2": "ws://115.191.4.103:34614",
"user16-1": "ws://115.191.4.103:34616",
"user16-2": "ws://115.191.4.103:34617",
"user17-1": "ws://115.191.4.103:34619",
"user17-2": "ws://115.191.4.103:34620",
"user18-1": "ws://115.191.4.103:34622",
"user18-2": "ws://115.191.4.103:34623",
"user19-1": "ws://115.191.4.103:34625",
"user19-2": "ws://115.191.4.103:34626",
"user20-1": "ws://115.191.4.103:34628",
"user20-2": "ws://115.191.4.103:34629",
"user21-1": "ws://115.191.4.103:34631",
"user21-2": "ws://115.191.4.103:34632",
"user22-1": "ws://115.191.4.103:34634",
"user22-2": "ws://115.191.4.103:34635",
"user23-1": "ws://115.191.4.103:34637",
"user23-2": "ws://115.191.4.103:34638",
"user24-1": "ws://115.191.4.103:34640",
"user24-2": "ws://115.191.4.103:34641",
"user25-1": "ws://115.191.4.103:34643",
"user25-2": "ws://115.191.4.103:34644",
"user26-1": "ws://115.191.4.103:34646",
"user26-2": "ws://115.191.4.103:34647",
"user27-1": "ws://115.191.4.103:34649",
"user27-2": "ws://115.191.4.103:34650",
"user28-1": "ws://115.191.4.103:34652",
"user28-2": "ws://115.191.4.103:34653",
"user29-1": "ws://115.191.4.103:34655",
"user29-2": "ws://115.191.4.103:34656",
"user30-1": "ws://115.191.4.103:34658",
"user30-2": "ws://115.191.4.103:34659",
"user31-1": "ws://115.191.4.103:34661",
"user31-2": "ws://115.191.4.103:34662",
"user32-1": "ws://115.191.4.103:34664",
"user32-2": "ws://115.191.4.103:34665",
"user33-1": "ws://115.191.4.103:34667",
"user33-2": "ws://115.191.4.103:34668",
"user34-1": "ws://115.191.4.103:34670",
"user34-2": "ws://115.191.4.103:34671",
"user35-1": "ws://115.191.4.103:34673",
"user35-2": "ws://115.191.4.103:34674",
"user36-1": "ws://115.191.4.103:34676",
"user36-2": "ws://115.191.4.103:34677",
"user37-1": "ws://115.191.4.103:34679",
"user37-2": "ws://115.191.4.103:34680",
"user38-1": "ws://115.191.4.103:34682",
"user38-2": "ws://115.191.4.103:34683",
"user39-1": "ws://115.191.4.103:34685",
"user39-2": "ws://115.191.4.103:34686",
"user40-1": "ws://115.191.4.103:34688",
"user40-2": "ws://115.191.4.103:34689",
"user41-1": "ws://115.191.4.103:34691",
"user41-2": "ws://115.191.4.103:34692",
"user42-1": "ws://115.191.4.103:34694",
"user42-2": "ws://115.191.4.103:34695",
"user43-1": "ws://115.191.4.103:34697",
"user43-2": "ws://115.191.4.103:34698",
"user44-1": "ws://115.191.4.103:34700",
"user44-2": "ws://115.191.4.103:34701",
"user45-1": "ws://115.191.4.103:34703",
"user45-2": "ws://115.191.4.103:34704",
"user46-1": "ws://115.191.4.103:34706",
"user46-2": "ws://115.191.4.103:34707",
"user47-1": "ws://115.191.4.103:34709",
"user47-2": "ws://115.191.4.103:34710",
"user48-1": "ws://115.191.4.103:34712",
"user48-2": "ws://115.191.4.103:34713",
"user49-1": "ws://115.191.4.103:34715",
"user49-2": "ws://115.191.4.103:34716",
"user50-1": "ws://115.191.4.103:34718",
"user50-2": "ws://115.191.4.103:34719",
"user51-1": "ws://115.191.4.103:34721",
"user51-2": "ws://115.191.4.103:34722",
"user52-1": "ws://115.191.4.103:34724",
"user52-2": "ws://115.191.4.103:34725",
"user53-1": "ws://115.191.4.103:34727",
"user53-2": "ws://115.191.4.103:34728",
"user54-1": "ws://115.191.4.103:34730",
"user54-2": "ws://115.191.4.103:34731",
"user55-1": "ws://115.191.4.103:34733",
"user55-2": "ws://115.191.4.103:34734",
"user56-1": "ws://115.191.4.103:34736",
"user56-2": "ws://115.191.4.103:34737",
"user57-1": "ws://115.191.4.103:34739",
"user57-2": "ws://115.191.4.103:34740",
"user58-1": "ws://115.191.4.103:34742",
"user58-2": "ws://115.191.4.103:34743",
"user59-1": "ws://115.191.4.103:34745",
"user59-2": "ws://115.191.4.103:34746",
"user60-1": "ws://115.191.4.103:34748",
"user60-2": "ws://115.191.4.103:34749",
"user61-1": "ws://115.191.4.103:34751",
"user61-2": "ws://115.191.4.103:34752",
"user62-1": "ws://115.191.4.103:34754",
"user62-2": "ws://115.191.4.103:34755",
"user63-1": "ws://115.191.4.103:34757",
"user63-2": "ws://115.191.4.103:34758",
"user64-1": "ws://115.191.4.103:34760",
"user64-2": "ws://115.191.4.103:34761",
"user65-1": "ws://115.191.4.103:34763",
"user65-2": "ws://115.191.4.103:34764",
"user66-1": "ws://115.191.4.103:34766",
"user66-2": "ws://115.191.4.103:34767",
"user67-1": "ws://115.191.4.103:34769",
"user67-2": "ws://115.191.4.103:34770",
"user68-1": "ws://115.191.4.103:34772",
"user68-2": "ws://115.191.4.103:34773",
"user69-1": "ws://115.191.4.103:34775",
"user69-2": "ws://115.191.4.103:34776",
"user70-1": "ws://115.191.4.103:34778",
"user70-2": "ws://115.191.4.103:34779",
"user71-1": "ws://115.191.4.103:34781",
"user71-2": "ws://115.191.4.103:34782",
"user72-1": "ws://115.191.4.103:34784",
"user72-2": "ws://115.191.4.103:34785",
"user73-1": "ws://115.191.4.103:34787",
"user73-2": "ws://115.191.4.103:34788",
"user74-1": "ws://115.191.4.103:34790",
"user74-2": "ws://115.191.4.103:34791",
"user75-1": "ws://115.191.4.103:34793",
"user75-2": "ws://115.191.4.103:34794",
"user76-1": "ws://115.191.4.103:34796",
"user76-2": "ws://115.191.4.103:34797",
"user77-1": "ws://115.191.4.103:34799",
"user77-2": "ws://115.191.4.103:34800",
"user78-1": "ws://115.191.4.103:34802",
"user78-2": "ws://115.191.4.103:34803",
"user79-1": "ws://115.191.4.103:34805",
"user79-2": "ws://115.191.4.103:34806",
"user80-1": "ws://115.191.4.103:34808",
"user80-2": "ws://115.191.4.103:34809",
"user81-1": "ws://115.191.4.103:34811",
"user81-2": "ws://115.191.4.103:34812",
"user82-1": "ws://115.191.4.103:34814",
"user82-2": "ws://115.191.4.103:34815",
"user83-1": "ws://115.191.4.103:34817",
"user83-2": "ws://115.191.4.103:34818",
"user84-1": "ws://115.191.4.103:34820",
"user84-2": "ws://115.191.4.103:34821",
"user85-1": "ws://115.191.4.103:34823",
"user85-2": "ws://115.191.4.103:34824",
"user86-1": "ws://115.191.4.103:34826",
"user86-2": "ws://115.191.4.103:34827",
"user87-1": "ws://115.191.4.103:34829",
"user87-2": "ws://115.191.4.103:34830",
"user88-1": "ws://115.191.4.103:34832",
"user88-2": "ws://115.191.4.103:34833",
"user89-1": "ws://115.191.4.103:34835",
"user89-2": "ws://115.191.4.103:34836",
"user90-1": "ws://115.191.4.103:34838",
"user90-2": "ws://115.191.4.103:34839",
"user91-1": "ws://115.191.4.103:34841",
"user91-2": "ws://115.191.4.103:34842",
"user92-1": "ws://115.191.4.103:34844",
"user92-2": "ws://115.191.4.103:34845",
"user93-1": "ws://115.191.4.103:34847",
"user93-2": "ws://115.191.4.103:34848",
"user94-1": "ws://115.191.4.103:34850",
"user94-2": "ws://115.191.4.103:34851",
"user95-1": "ws://115.191.4.103:34853",
"user95-2": "ws://115.191.4.103:34854",
"user96-1": "ws://115.191.4.103:34856",
"user96-2": "ws://115.191.4.103:34857",
"user97-1": "ws://115.191.4.103:34859",
"user97-2": "ws://115.191.4.103:34860",
"user98-1": "ws://115.191.4.103:34862",
"user98-2": "ws://115.191.4.103:34863",
"user99-1": "ws://115.191.4.103:34865",
"user99-2": "ws://115.191.4.103:34866"
}
}
@@ -0,0 +1,37 @@
{ "compressionlevel":0,
"editorsettings":
{
"export":
{
"format":"json",
"target":"tilemap.json"
}
},
"height":20,
"infinite":false,
"layers":[],
"nextlayerid":4,
"nextobjectid":1,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.3.0",
"tileheight":32,
"tilesets":[
{
"columns":12,
"firstgid":1,
"image":"tiles.png",
"imageheight":352,
"imagewidth":384,
"margin":0,
"name":"tiles",
"spacing":0,
"tilecount":132,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.2,
"width":20
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+37
View File
@@ -0,0 +1,37 @@
{ "compressionlevel":0,
"editorsettings":
{
"export":
{
"format":"json",
"target":"tilemap.json"
}
},
"height":20,
"infinite":false,
"layers":[],
"nextlayerid":4,
"nextobjectid":1,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.3.0",
"tileheight":32,
"tilesets":[
{
"columns":12,
"firstgid":1,
"image":"tiles.png",
"imageheight":352,
"imagewidth":384,
"margin":0,
"name":"tiles",
"spacing":0,
"tilecount":132,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.2,
"width":20
}
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.3.0" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="21" height="15" tilewidth="32" tileheight="32" infinite="0" nextlayerid="4" nextobjectid="1">
<editorsettings>
<export target="tilemap.json" format="json"/>
</editorsettings>
<tileset firstgid="1" name="tiles" tilewidth="32" tileheight="32" tilecount="132" columns="12">
<image source="tiles.png" width="384" height="352"/>
</tileset>
<layer id="2" name="level" width="21" height="15">
<data encoding="csv">
45,46,46,46,46,46,46,46,46,47,5,45,46,46,46,46,46,46,46,46,47,
57,94,94,94,94,94,94,94,94,60,17,60,94,94,94,94,94,94,94,94,59,
57,106,81,83,94,81,46,47,94,69,46,71,94,45,46,83,94,81,83,106,59,
57,94,94,94,94,94,94,60,94,94,96,94,94,60,94,94,94,94,94,94,59,
57,94,81,83,94,48,94,69,46,83,0,81,46,71,94,48,94,81,83,94,59,
60,94,94,94,94,60,94,94,0,0,0,0,0,94,94,60,94,94,94,94,60,
69,46,46,83,94,69,46,47,0,53,56,55,0,45,46,71,94,81,46,46,71,
104,0,0,0,94,94,94,60,0,65,68,67,0,60,94,94,94,0,0,0,104,
45,46,46,83,94,48,94,72,0,77,79,80,0,72,94,48,94,81,46,46,47,
60,94,94,94,94,60,94,0,0,0,95,0,0,0,94,60,94,94,94,94,60,
57,94,81,47,94,72,94,48,94,81,46,83,94,48,94,72,94,45,83,94,59,
57,106,94,60,94,94,94,60,94,94,84,94,94,60,94,94,94,60,94,106,59,
69,47,94,72,94,81,83,69,83,94,94,94,81,71,81,83,94,72,94,45,71,
5,60,94,94,94,94,94,94,94,94,58,94,94,94,94,94,94,94,94,60,5,
17,69,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,71,17
</data>
</layer>
</map>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

+213
View File
@@ -0,0 +1,213 @@
{
"teams": [
{ "name": "L", "who": "user11-1"},
{ "name": "R", "who": "human"}
],
"setup": {
"numPlayers": 1,
"numFlags": 3,
"useRandomFlags": false
},
"servers": {
"user0-1": "ws://115.191.4.103:34568",
"user0-2": "ws://115.191.4.103:34569",
"user1-1": "ws://115.191.4.103:34571",
"user1-2": "ws://115.191.4.103:34572",
"user2-1": "ws://115.191.4.103:34574",
"user2-2": "ws://115.191.4.103:34575",
"user3-1": "ws://115.191.4.103:34577",
"user3-2": "ws://115.191.4.103:34578",
"user4-1": "ws://115.191.4.103:34580",
"user4-2": "ws://115.191.4.103:34581",
"user5-1": "ws://115.191.4.103:34583",
"user5-2": "ws://115.191.4.103:34584",
"user6-1": "ws://115.191.4.103:34586",
"user6-2": "ws://115.191.4.103:34587",
"user7-1": "ws://115.191.4.103:34589",
"user7-2": "ws://115.191.4.103:34590",
"user8-1": "ws://115.191.4.103:34592",
"user8-2": "ws://115.191.4.103:34593",
"user9-1": "ws://115.191.4.103:34595",
"user9-2": "ws://115.191.4.103:34596",
"user10-1": "ws://115.191.4.103:34598",
"user10-2": "ws://115.191.4.103:34599",
"user11-1": "ws://115.191.4.103:34601",
"user11-2": "ws://115.191.4.103:34602",
"user12-1": "ws://115.191.4.103:34604",
"user12-2": "ws://115.191.4.103:34605",
"user13-1": "ws://115.191.4.103:34607",
"user13-2": "ws://115.191.4.103:34608",
"user14-1": "ws://115.191.4.103:34610",
"user14-2": "ws://115.191.4.103:34611",
"user15-1": "ws://115.191.4.103:34613",
"user15-2": "ws://115.191.4.103:34614",
"user16-1": "ws://115.191.4.103:34616",
"user16-2": "ws://115.191.4.103:34617",
"user17-1": "ws://115.191.4.103:34619",
"user17-2": "ws://115.191.4.103:34620",
"user18-1": "ws://115.191.4.103:34622",
"user18-2": "ws://115.191.4.103:34623",
"user19-1": "ws://115.191.4.103:34625",
"user19-2": "ws://115.191.4.103:34626",
"user20-1": "ws://115.191.4.103:34628",
"user20-2": "ws://115.191.4.103:34629",
"user21-1": "ws://115.191.4.103:34631",
"user21-2": "ws://115.191.4.103:34632",
"user22-1": "ws://115.191.4.103:34634",
"user22-2": "ws://115.191.4.103:34635",
"user23-1": "ws://115.191.4.103:34637",
"user23-2": "ws://115.191.4.103:34638",
"user24-1": "ws://115.191.4.103:34640",
"user24-2": "ws://115.191.4.103:34641",
"user25-1": "ws://115.191.4.103:34643",
"user25-2": "ws://115.191.4.103:34644",
"user26-1": "ws://115.191.4.103:34646",
"user26-2": "ws://115.191.4.103:34647",
"user27-1": "ws://115.191.4.103:34649",
"user27-2": "ws://115.191.4.103:34650",
"user28-1": "ws://115.191.4.103:34652",
"user28-2": "ws://115.191.4.103:34653",
"user29-1": "ws://115.191.4.103:34655",
"user29-2": "ws://115.191.4.103:34656",
"user30-1": "ws://115.191.4.103:34658",
"user30-2": "ws://115.191.4.103:34659",
"user31-1": "ws://115.191.4.103:34661",
"user31-2": "ws://115.191.4.103:34662",
"user32-1": "ws://115.191.4.103:34664",
"user32-2": "ws://115.191.4.103:34665",
"user33-1": "ws://115.191.4.103:34667",
"user33-2": "ws://115.191.4.103:34668",
"user34-1": "ws://115.191.4.103:34670",
"user34-2": "ws://115.191.4.103:34671",
"user35-1": "ws://115.191.4.103:34673",
"user35-2": "ws://115.191.4.103:34674",
"user36-1": "ws://115.191.4.103:34676",
"user36-2": "ws://115.191.4.103:34677",
"user37-1": "ws://115.191.4.103:34679",
"user37-2": "ws://115.191.4.103:34680",
"user38-1": "ws://115.191.4.103:34682",
"user38-2": "ws://115.191.4.103:34683",
"user39-1": "ws://115.191.4.103:34685",
"user39-2": "ws://115.191.4.103:34686",
"user40-1": "ws://115.191.4.103:34688",
"user40-2": "ws://115.191.4.103:34689",
"user41-1": "ws://115.191.4.103:34691",
"user41-2": "ws://115.191.4.103:34692",
"user42-1": "ws://115.191.4.103:34694",
"user42-2": "ws://115.191.4.103:34695",
"user43-1": "ws://115.191.4.103:34697",
"user43-2": "ws://115.191.4.103:34698",
"user44-1": "ws://115.191.4.103:34700",
"user44-2": "ws://115.191.4.103:34701",
"user45-1": "ws://115.191.4.103:34703",
"user45-2": "ws://115.191.4.103:34704",
"user46-1": "ws://115.191.4.103:34706",
"user46-2": "ws://115.191.4.103:34707",
"user47-1": "ws://115.191.4.103:34709",
"user47-2": "ws://115.191.4.103:34710",
"user48-1": "ws://115.191.4.103:34712",
"user48-2": "ws://115.191.4.103:34713",
"user49-1": "ws://115.191.4.103:34715",
"user49-2": "ws://115.191.4.103:34716",
"user50-1": "ws://115.191.4.103:34718",
"user50-2": "ws://115.191.4.103:34719",
"user51-1": "ws://115.191.4.103:34721",
"user51-2": "ws://115.191.4.103:34722",
"user52-1": "ws://115.191.4.103:34724",
"user52-2": "ws://115.191.4.103:34725",
"user53-1": "ws://115.191.4.103:34727",
"user53-2": "ws://115.191.4.103:34728",
"user54-1": "ws://115.191.4.103:34730",
"user54-2": "ws://115.191.4.103:34731",
"user55-1": "ws://115.191.4.103:34733",
"user55-2": "ws://115.191.4.103:34734",
"user56-1": "ws://115.191.4.103:34736",
"user56-2": "ws://115.191.4.103:34737",
"user57-1": "ws://115.191.4.103:34739",
"user57-2": "ws://115.191.4.103:34740",
"user58-1": "ws://115.191.4.103:34742",
"user58-2": "ws://115.191.4.103:34743",
"user59-1": "ws://115.191.4.103:34745",
"user59-2": "ws://115.191.4.103:34746",
"user60-1": "ws://115.191.4.103:34748",
"user60-2": "ws://115.191.4.103:34749",
"user61-1": "ws://115.191.4.103:34751",
"user61-2": "ws://115.191.4.103:34752",
"user62-1": "ws://115.191.4.103:34754",
"user62-2": "ws://115.191.4.103:34755",
"user63-1": "ws://115.191.4.103:34757",
"user63-2": "ws://115.191.4.103:34758",
"user64-1": "ws://115.191.4.103:34760",
"user64-2": "ws://115.191.4.103:34761",
"user65-1": "ws://115.191.4.103:34763",
"user65-2": "ws://115.191.4.103:34764",
"user66-1": "ws://115.191.4.103:34766",
"user66-2": "ws://115.191.4.103:34767",
"user67-1": "ws://115.191.4.103:34769",
"user67-2": "ws://115.191.4.103:34770",
"user68-1": "ws://115.191.4.103:34772",
"user68-2": "ws://115.191.4.103:34773",
"user69-1": "ws://115.191.4.103:34775",
"user69-2": "ws://115.191.4.103:34776",
"user70-1": "ws://115.191.4.103:34778",
"user70-2": "ws://115.191.4.103:34779",
"user71-1": "ws://115.191.4.103:34781",
"user71-2": "ws://115.191.4.103:34782",
"user72-1": "ws://115.191.4.103:34784",
"user72-2": "ws://115.191.4.103:34785",
"user73-1": "ws://115.191.4.103:34787",
"user73-2": "ws://115.191.4.103:34788",
"user74-1": "ws://115.191.4.103:34790",
"user74-2": "ws://115.191.4.103:34791",
"user75-1": "ws://115.191.4.103:34793",
"user75-2": "ws://115.191.4.103:34794",
"user76-1": "ws://115.191.4.103:34796",
"user76-2": "ws://115.191.4.103:34797",
"user77-1": "ws://115.191.4.103:34799",
"user77-2": "ws://115.191.4.103:34800",
"user78-1": "ws://115.191.4.103:34802",
"user78-2": "ws://115.191.4.103:34803",
"user79-1": "ws://115.191.4.103:34805",
"user79-2": "ws://115.191.4.103:34806",
"user80-1": "ws://115.191.4.103:34808",
"user80-2": "ws://115.191.4.103:34809",
"user81-1": "ws://115.191.4.103:34811",
"user81-2": "ws://115.191.4.103:34812",
"user82-1": "ws://115.191.4.103:34814",
"user82-2": "ws://115.191.4.103:34815",
"user83-1": "ws://115.191.4.103:34817",
"user83-2": "ws://115.191.4.103:34818",
"user84-1": "ws://115.191.4.103:34820",
"user84-2": "ws://115.191.4.103:34821",
"user85-1": "ws://115.191.4.103:34823",
"user85-2": "ws://115.191.4.103:34824",
"user86-1": "ws://115.191.4.103:34826",
"user86-2": "ws://115.191.4.103:34827",
"user87-1": "ws://115.191.4.103:34829",
"user87-2": "ws://115.191.4.103:34830",
"user88-1": "ws://115.191.4.103:34832",
"user88-2": "ws://115.191.4.103:34833",
"user89-1": "ws://115.191.4.103:34835",
"user89-2": "ws://115.191.4.103:34836",
"user90-1": "ws://115.191.4.103:34838",
"user90-2": "ws://115.191.4.103:34839",
"user91-1": "ws://115.191.4.103:34841",
"user91-2": "ws://115.191.4.103:34842",
"user92-1": "ws://115.191.4.103:34844",
"user92-2": "ws://115.191.4.103:34845",
"user93-1": "ws://115.191.4.103:34847",
"user93-2": "ws://115.191.4.103:34848",
"user94-1": "ws://115.191.4.103:34850",
"user94-2": "ws://115.191.4.103:34851",
"user95-1": "ws://115.191.4.103:34853",
"user95-2": "ws://115.191.4.103:34854",
"user96-1": "ws://115.191.4.103:34856",
"user96-2": "ws://115.191.4.103:34857",
"user97-1": "ws://115.191.4.103:34859",
"user97-2": "ws://115.191.4.103:34860",
"user98-1": "ws://115.191.4.103:34862",
"user98-2": "ws://115.191.4.103:34863",
"user99-1": "ws://115.191.4.103:34865",
"user99-2": "ws://115.191.4.103:34866"
}
}
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>capture_the_flag</title>
<style>
/* Reset CSS: Please avoid making changes here unless you know what you're doing :) */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
}
body {
line-height: 1;
font-family: Arial, sans-serif;
background-color: #040218;
}
ol, ul {
list-style: none;
}
img, video {
max-width: 100%;
height: auto;
display: block;
}
a {
text-decoration: none;
color: inherit;
}
</style>
</head>
<body>
<div id="game-container">
</div>
<!--script src="./phaser.js"></script-->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
<script type="module" src="./src/main.js"></script>
</body>
</html>
+248252
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
[game]
title = "capture_the_flag"
width = 1280
height = 720
[user]
id = "c2d44495-b627-44a5-8415-f3cfe6b96dcb"
[editor]
editor_version = "1.1.2"
phaser_version = "3.88.2"
creation_at = 1756965006742
code_font_size = 16
+74
View File
@@ -0,0 +1,74 @@
export default {
// 'audio': {
// score: {
// key: 'sound',
// args: ['assets/sound.mp3', 'assets/sound.m4a', 'assets/sound.ogg']
// },
// },
// 'image': {
// spikes: {
// key: 'spikes',
// args: ['assets/spikes.png']
// },
// },
'image': {
red_flag_img: {
key: 'red_flag_img',
args: ['assets/red_flag_32_32.png']
},
yellow_flag_img: {
key: 'yellow_flag_img',
args: ['assets/yellow_flag_32_32.png']
},
},
'spritesheet': {
tiles: {
key: 'tiles',
args: ['assets/tiles.png', {
frameWidth: 32,
frameHeight: 32
}]
},
characters: {
key: 'characters',
args: ['assets/characters.png', {
frameWidth: 32,
frameHeight: 32
}]
},
characters_L_flag: {
key: 'characters_L_flag',
args: ['assets/characters_yellow_flag.png', {
frameWidth: 32,
frameHeight: 32
}]
},
characters_R_flag: {
key: 'characters_R_flag',
args: ['assets/characters_red_flag.png', {
frameWidth: 32,
frameHeight: 32
}]
},
L_flag: {
key: 'L_flag',
args: ['assets/red_flag_32_32.png', {
frameWidth: 32,
frameHeight: 32
}]
},
R_flag: {
key: 'R_flag',
args: ['assets/yellow_flag_32_32.png', {
frameWidth: 32,
frameHeight: 32
}]
},
},
'tilemapTiledJSON': {
map: {
key: 'map',
args: ['assets/tilemap.json']
}
},
};
+42
View File
@@ -0,0 +1,42 @@
import ASSETS from '../assets.js';
export default class Flag extends Phaser.Physics.Arcade.Sprite
{
constructor(scene, x, y, team, canPickup)
{
if (team == "L") {
super(scene, x, y, ASSETS.spritesheet.L_flag.key);
} else {
super(scene, x, y, ASSETS.spritesheet.R_flag.key);
}
scene.add.existing(this);
scene.physics.add.existing(this);
this.team = team;
this.mapOffset = scene.getMapOffset();
this.posX = x;
this.posY = y;
this.setPosition(this.mapOffset.x + (x * this.mapOffset.tileSize), this.mapOffset.y + (y * this.mapOffset.tileSize));
this.setDepth(90);
this.scene = scene;
this.canPickup = canPickup;
}
collect() {
if (!this.canPickup) {
return false;
}
this.scene.removeFlagItem(this);
return true;
}
// return player status to send to remote backend
getStatus() {
return {
"canPickup": this.canPickup,
"posX": this.posX,
"posY": this.posY,
}
}
}
+166
View File
@@ -0,0 +1,166 @@
import ASSETS from '../assets.js';
const PlayerDirection = Object.freeze({
UP: "up",
DOWN: "down",
LEFT: "left",
RIGHT: "right",
});
export default class Player extends Phaser.Physics.Arcade.Sprite {
moveSpeed = 300; // time in milliseconds to move from one tile to another
frameDuration = 0;
accumulator = 0;
target = { x: 0, y: 0 };
constructor(scene, name, x, y, team, spriteChoice = 1, useAWSD = true) {
super(scene, x, y, ASSETS.spritesheet.characters.key, (spriteChoice - 1) * 12 + 1);
scene.add.existing(this);
scene.physics.add.existing(this);
this.name = name;
this.team = team;
this.inPrison = false;
this.inPrisonTimeLeft = 0;
this.inPrisonDuration = 20000; // time in milliseconds to stay in prison unless a teammate saves the player.
this.hasFlag = false;
this.spriteChoice = spriteChoice;
this.mapOffset = scene.getMapOffset();
this.target.x = this.mapOffset.x + (x * this.mapOffset.tileSize);
this.target.y = this.mapOffset.y + (y * this.mapOffset.tileSize);
this.setPosition(this.target.x, this.target.y);
this.setCollideWorldBounds(true);
this.setDepth(100);
this.scene = scene;
this.frameDuration = this.moveSpeed / this.mapOffset.tileSize;
this.remoteControl = null;
// key control
if (useAWSD) {
this.keys = this.scene.awsd_keys;
} else {
this.keys = this.scene.cursors;
}
this.can_go_next_tile = false; // will go next tile only if this is true
}
collectFlag() {
this.hasFlag = true;
}
dropFlag() {
this.hasFlag = false;
}
setRemoteControl(remoteControl) {
this.remoteControl = remoteControl;
}
update(time, delta) {
this.accumulator += delta;
while (this.accumulator > this.frameDuration) {
this.accumulator -= this.frameDuration;
if (this.inPrison) {
this.inPrisonTimeLeft -= this.frameDuration;
if (this.inPrisonTimeLeft <= 0) {
this.inPrison = false;
this.inPrisonTimeLeft = 0;
}
}
if (!this.inPrison) {
this.checkInput();
this.move();
} else {
this.showStaticImage();
}
}
}
checkInput() {
// check if player is at target position
if (this.can_go_next_tile && this.target.x === this.x && this.target.y === this.y) {
this.can_go_next_tile = false;
const moveDirection = { x: 0, y: 0 }; // default move direction
// Keys take priority over heuristics
if (this.keys.left.isDown) moveDirection.x--;
else if (this.keys.right.isDown) moveDirection.x++;
else if (this.keys.up.isDown) moveDirection.y--;
else if (this.keys.down.isDown) moveDirection.y++;
else if (this.remoteControl == PlayerDirection.LEFT) moveDirection.x--;
else if (this.remoteControl == PlayerDirection.RIGHT) moveDirection.x++;
else if (this.remoteControl == PlayerDirection.UP) moveDirection.y--;
else if (this.remoteControl == PlayerDirection.DOWN) moveDirection.y++;
// set next tile coordinates to move towards
const nextPosition = {
x: this.x + (moveDirection.x * this.mapOffset.tileSize),
y: this.y + (moveDirection.y * this.mapOffset.tileSize)
};
// check if next tile to move towards is walkable
if (!this.scene.isWall(nextPosition.x, nextPosition.y)) {
// set target position to move towards
this.target.x = nextPosition.x;
this.target.y = nextPosition.y;
}
}
}
// move player towards target position
move() {
let animation_key = "player" + this.spriteChoice + (this.hasFlag ? "-characters_"+this.team+"_flag-": "-characters-");
if (this.x < this.target.x) {
this.x ++;
this.anims.play(animation_key + "right", true);
}
else if (this.x > this.target.x) {
this.x --;
this.anims.play(animation_key + "left", true);
}
if (this.y < this.target.y) {
this.y ++;
this.anims.play(animation_key + "down", true);
}
else if (this.y > this.target.y) {
this.y --;
this.anims.play(animation_key + "up", true);
}
}
showStaticImage() {
let animation_key = "player" + this.spriteChoice + "-characters-down";
this.anims.play(animation_key, true);
}
toPrison(prisonX, prisonY) {
this.target.x = this.mapOffset.x + (prisonX * this.mapOffset.tileSize);
this.target.y = this.mapOffset.y + (prisonY * this.mapOffset.tileSize);
this.setPosition(this.target.x, this.target.y);
this.inPrison = true;
this.inPrisonTimeLeft = this.inPrisonDuration;
}
hit() {
this.destroy();
}
// return player status to send to remote backend
getStatus() {
return {
"name": this.name,
"team": this.team,
"hasFlag": this.hasFlag,
"posX": (this.target.x - this.mapOffset.x) / this.mapOffset.tileSize,
"posY": (this.target.y - this.mapOffset.y) / this.mapOffset.tileSize,
"inPrison": this.inPrison,
"inPrisonTimeLeft": this.inPrisonTimeLeft,
"inPrisonDuration": this.inPrisonDuration,
}
}
}
+34
View File
@@ -0,0 +1,34 @@
import { Boot } from './scenes/Boot.js';
import { Preloader } from './scenes/Preloader.js';
import { Game } from './scenes/Game.js';
import { GameOver } from './scenes/GameOver.js';
// Find out more information about the Game Config at:
// https://newdocs.phaser.io/docs/3.70.0/Phaser.Types.Core.GameConfig
const config = {
type: Phaser.AUTO,
// REMEMBER to keep consistent with tilemap.json
width: (20 + 10) * 32,
height: (20 + 10) * 32,
parent: 'game-container',
backgroundColor: '#2d3436',
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: { y: 0 }
}
},
scene: [
Boot,
Preloader,
Game,
GameOver
]
};
new Phaser.Game(config);
+16
View File
@@ -0,0 +1,16 @@
export class Boot extends Phaser.Scene {
constructor() {
super('Boot');
}
preload() {
// The Boot Scene is typically used to load in any assets you require for your Preloader, such as a game logo or background.
// The smaller the file size of the assets, the better, as the Boot Scene itself has no preloader.
// this.load.image('background', 'assets/background.png');
}
create() {
this.scene.start('Preloader');
}
}
+821
View File
@@ -0,0 +1,821 @@
/*
* Asset from: https://kenney.nl/assets/pixel-platformer
*/
import ASSETS from '../assets.js';
import Player from '../gameObjects/Player.js';
import PlayerDirection from '../gameObjects/Player.js';
import Flag from '../gameObjects/Flag.js';
export class Game extends Phaser.Scene {
constructor() {
super('Game');
}
create() {
// do nothing
// we only do in create_after_preload()
}
create_after_preload() {
// constant
// this.NUM_PLAYERS = 3;
// this.NUM_FLAGS = 9;
// this.useRandomFlags = false;
this.NUM_OBSTACLES_1 = 8;
this.NUM_OBSTACLES_2 = 4;
this.stageSent = false;
this.initVariables();
this.initGameUi();
this.initAnimations();
this.initInput();
this.initMap();
this.initBoundary();
this.initTeams();
this.initPhysics();
}
startOrPauseOrContinue() {
if (!this.gameStarted) {
this.startGame();
} else {
this.gamePaused = !this.gamePaused;
}
}
update(time, delta) {
if (!this.gameStarted || this.gamePaused) return;
// move players
let players_ready = 0, total_players = 0;
let tick = (/** @type {string} */ can_go_next_tile) => {
this.lteamPlayers.getChildren().forEach( player => {
++total_players;
player.can_go_next_tile |= can_go_next_tile;
player.update(time, delta);
if(player.x == player.target.x && player.y == player.target.y)
++players_ready;
});
this.rteamPlayers.getChildren().forEach( player => {
++total_players;
player.can_go_next_tile |= can_go_next_tile;
player.update(time, delta);
if(player.x == player.target.x && player.y == player.target.y)
++players_ready;
});
};
tick(false);
if(players_ready !== total_players) {
this.stageSent = false;
return;
}
tick(true);
if (this.stageSent && time - this.lastSendTime < 600)
return;
setTimeout(() => {
this.stageSent = true;
// notify server backend
let lteamPlayerStatus = this.lteamPlayers.getChildren().map( player => player.getStatus() );
let lteamFlagStatus = this.lteamFlags.getChildren().map( flag => flag.getStatus() );
let rteamPlayerStatus = this.rteamPlayers.getChildren().map( player => player.getStatus() );
let rteamFlagStatus = this.rteamFlags.getChildren().map( flag => flag.getStatus() );
// each team gets its own perspective
if (this.lteamSocket && this.lteamSocket.readyState == WebSocket.OPEN) {
const lteamPlayerStatus = this.lteamPlayers.getChildren().map( player => player.getStatus() );
const payload = {
action: "status",
time: time,
myteamPlayer: lteamPlayerStatus,
myteamFlag: lteamFlagStatus,
myteamScore: this.lteamState.score,
opponentPlayer: rteamPlayerStatus,
opponentFlag: rteamFlagStatus,
opponentScore: this.rteamState.score,
}
this.lteamSocket.send(JSON.stringify(payload));
}
if (this.rteamSocket && this.rteamSocket.readyState == WebSocket.OPEN) {
const payload = {
action: "status",
time: time,
myteamPlayer: rteamPlayerStatus,
myteamFlag: rteamFlagStatus,
myteamScore: this.rteamState.score,
opponentPlayer: lteamPlayerStatus,
opponentFlag: lteamFlagStatus,
opponentScore: this.lteamState.score,
}
this.rteamSocket.send(JSON.stringify(payload));
}
this.lastSendTime = time;
}, 50);
}
async preload() {
// load team info from JSON file
const resp = await fetch("game_config.json");
const data = await resp.json();
this.team_config = data.teams;
this.team_servers = data.servers;
this.initSockets();
this.NUM_PLAYERS = data.setup.numPlayers;
this.NUM_FLAGS = data.setup.numFlags;
this.useRandomFlags = data.setup.useRandomFlags;
this.create_after_preload();
}
updatePlayerInfo(teamName, data) {
try {
const actions = JSON.parse(data);
Object.keys(actions.players).forEach (p => {
let d = actions.players[p];
console.assert(p.startsWith(teamName), `Invalid operation to control player ${p} for team ${teamName}`);
console.assert(d == "up" || d == "down" || d == "left" || d == "right",
`Invalid operation to move player to direction ${d}`);
});
let teamPlayers = null;
if (teamName === "L") {
teamPlayers = this.lteamPlayers.getChildren();
} else if (teamName === "R") {
teamPlayers = this.rteamPlayers.getChildren();
}
// For each team player, we will set its direction.
teamPlayers.forEach ( player => {
let remoteControl = actions.players[player.name];
player.setRemoteControl(remoteControl);
})
} catch (e) {
console.error("Invalid JSON from backend:", e, data);
}
}
initSockets() {
this.lteamSocket = null;
this.rteamSocket = null;
let lTeamWho = "-";
let rTeamWho = "-"
// connect to LTeam and RTeam backends
for (let i = 0; i < this.team_config.length; ++i) {
const team = this.team_config[i];
if (team.name != "L" && team.name != "R") {
console.log(`Unknown team ${team.name} found in remote_config.json. Skip.`);
continue;
}
if (team["ws_url"] == null && (team["who"] == null || this.team_servers[team.who] == null)) {
console.log(`Unknown server ${team["who"]} for ${team.name} found in remote_config.json. Skip.`);
continue;
}
if (team["who"] != null) {
if (team.name == "L") {
lTeamWho = team.who;
} else {
rTeamWho = team.who;
}
}
let ws = team["ws_url"] == null ? new WebSocket(this.team_servers[team.who]) : new WebSocket(team.ws_url);
ws.onopen = () => console.log(`${team.name} connected`);
ws.onmessage = (msg) => this.updatePlayerInfo(team.name, msg.data);
ws.onerror = (err) => console.error("WebSocket error", err);
if (team.name === "L") {
this.lteamSocket = ws;
}
else if (team.name === "R") {
this.rteamSocket = ws;
}
};
this.lastSendTime = 0;
this.lTeamWhoText = this.add.text(30, 60, `${lTeamWho}`, {
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
stroke: '#000000', strokeThickness: 8, align: 'left'
})
.setDepth(100);
this.rTeamWhoText = this.add.text(this.scale.width - 450, 60, `${rTeamWho}`, {
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
stroke: '#000000', strokeThickness: 8, align: 'right'
})
.setDepth(100);
}
initVariables() {
this.gameStarted = false;
this.gamePaused = false;
this.centerX = this.scale.width * 0.5;
this.centerY = this.scale.height * 0.5;
this.tileSize = 32; // width and height of a tile in pixels
this.halfTileSize = this.tileSize * 0.5; // width and height of a tile in pixels
this.mapHeight = (this.scale.height / this.tileSize) - 5 * 2; // height of the tile map (in tiles)
this.mapWidth = (this.scale.width / this.tileSize) - 5 * 2; // width of the tile map (in tiles)
this.mapX = this.centerX - (this.mapWidth * this.tileSize * 0.5); // x position of the top-left corner of the tile map
this.mapY = this.centerY - (this.mapHeight * this.tileSize * 0.5); // y position of the top-left corner of the tile map
// used to generate random background image
this.backgroundTiles = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 44 ];
this.targetTiles = [13, 14, 15, 25, 26, 27, 37, 38, 39];
this.prisonTiles = [97, 98, 99, 109, 110, 111, 121, 122, 123];
this.wallTiles = [45, 46, 47, 57, 59, 69, 70, 71];
this.tree1Tiles = [6, 18, 30, 29, 28];
this.tree2Tiles = [[4, 16], [5, 17]];
// generate walls
this.walls = [
{x: 0, y: 0, tileId: 45}, {x: this.mapWidth -1, y: 0, tileId: 47}, {x: 0, y: this.mapHeight - 1, tileId:69}, {x: this.mapWidth - 1, y: this.mapHeight - 1, tileId: 71}
].concat(
Array.from({ length: this.mapWidth - 2 }, (_, i) => ({ x: i + 1, y: 0, tileId: 46 }))
).concat(
Array.from({ length: this.mapWidth - 2 }, (_, i) => ({ x: i + 1, y: this.mapHeight - 1, tileId: 46 }))
).concat(
Array.from({ length: this.mapHeight - 2 }, (_, i) => ({ x: 0, y: i + 1, tileId: 57 }))
).concat(
Array.from({ length: this.mapHeight - 2 }, (_, i) => ({ x: this.mapWidth - 1, y: i + 1, tileId: 59 }))
);
function notContains(xyArrays, x, y) {
const ret = xyArrays.find(obj => (obj.x === x && obj.y === y));
return ret == null;
}
// generate obstacles
this.obstacles1 = [];
for (let i = 0; i < this.NUM_OBSTACLES_1; ++i) {
while (true) {
const x = Phaser.Math.RND.integerInRange(4, this.mapWidth - 5);
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 2);
if (notContains(this.obstacles1, x, y)) {
this.obstacles1.push({x: x, y: y});
break;
}
}
}
this.obstacles2 = [];
for (let i = 0; i< this.NUM_OBSTACLES_2; ++i) {
while (true) {
const x = Phaser.Math.RND.integerInRange(4, this.mapWidth - 5);
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 3);
if (notContains(this.obstacles1, x, y)
&& notContains(this.obstacles1, x, y + 1)
&& notContains(this.obstacles2, x, y - 1)
&& notContains(this.obstacles2, x, y)) {
this.obstacles2.push({x: x, y: y});
break;
}
}
}
// Randomly generate flags
let lFlags = [];
for (let i = 0; i< this.NUM_FLAGS; ++i) {
while (true) {
const x = Phaser.Math.RND.integerInRange(2, this.mapWidth / 2 - 1);
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 3);
if (notContains(this.obstacles1, x, y)
&& notContains(this.obstacles2, x, y - 1)
&& notContains(this.obstacles2, x, y)
&& notContains(lFlags, x, y)) {
lFlags.push({x: x, y: y});
break;
}
}
}
let rFlags = [];
for (let i = 0; i< this.NUM_FLAGS; ++i) {
while (true) {
const x = Phaser.Math.RND.integerInRange(this.mapWidth / 2, this.mapWidth - 2);
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 3);
if (notContains(this.obstacles1, x, y)
&& notContains(this.obstacles2, x, y - 1)
&& notContains(this.obstacles2, x, y)
&& notContains(rFlags, x, y)) {
rFlags.push({x: x, y: y});
break;
}
}
}
// generate flag and player position for LTeam and RTeam
// left team
this.lteamState = {
score: 0,
player_sprite_choice: 1,
flags: this.useRandomFlags ? lFlags : Array.from({ length: this.NUM_FLAGS }, (_, i) => ({ x: 1, y: i + 1})),
players: this.useRandomFlags? Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: 1, y: i + 1, name: "L" + i})) : Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: 2, y: i + 1, name: "L" + i})),
target: this.create3x3grid(2, this.mapHeight / 2),
prison: this.create3x3grid(2, this.mapHeight - 3),
};
// right team
this.rteamState = {
score: 0,
player_sprite_choice: 4,
// flags: Array.from({ length: this.NUM_FLAGS }, (_, i) => ({ x: this.mapWidth - 2, y: i + 1})),
flags: this.useRandomFlags ? rFlags : Array.from({ length: this.NUM_FLAGS }, (_, i) => ({ x: this.mapWidth - 2, y: i + 1})),
players: this.useRandomFlags ? Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: this.mapWidth - 2, y: i + 1, name: "R" + i})) : Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: this.mapWidth - 3, y: i + 1, name: "R" + i})),
target: this.create3x3grid(this.mapWidth - 3, this.mapHeight / 2),
prison: this.create3x3grid(this.mapWidth - 3, this.mapHeight - 3),
};
this.map; // rference to tile map
this.groundLayer; // used to create background layer of tile map
this.levelLayer; // reference to level layer of tile map
}
initGameUi() {
// Create tutorial text
this.tutorialText = this.add.text(this.centerX, this.centerY, 'Arrow keys to move!\nPress Spacebar to Start', {
fontFamily: 'Arial Black', fontSize: 48, color: '#ffffff',
stroke: '#000000', strokeThickness: 8,
align: 'center'
})
.setOrigin(0.5)
.setDepth(100);
// Create score text
this.lScoreText = this.add.text(30, 20, 'LTeam #Flags: 0', {
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
stroke: '#000000', strokeThickness: 8, align: 'left'
})
.setDepth(100);
this.rScoreText = this.add.text(this.scale.width - 450, 20, 'RTeam #Flags: 0', {
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
stroke: '#000000', strokeThickness: 8, align: 'right'
})
.setDepth(100);
// Create game over text
this.gameOverText = this.add.text(this.scale.width * 0.5, this.scale.height * 0.5, 'Game Over', {
fontFamily: 'Arial Black', fontSize: 64, color: '#ffffff',
stroke: '#000000', strokeThickness: 8,
align: 'center'
})
.setOrigin(0.5)
.setDepth(100)
.setVisible(false);
}
initAnimations() {
const flag_choices = ["characters", "characters_L_flag", "characters_R_flag"];
const dir_choices = ["left", "down", "up", "right"]
for (let k = 0; k < 3; ++k) {
for (let i = 1; i <= 6; ++i) {
for (let j = 0; j < 4; ++j) {
const key = "player" + i + "-" + flag_choices[k] + "-" + dir_choices[j];
const config = { frames: [(i - 1) * 12 + j, (i - 1) * 12 + j + 4, (i - 1) * 12 + j + 8] };
this.anims.create({
key: key,
frames: this.anims.generateFrameNumbers(flag_choices[k], config),
frameRate: 10,
repeat: 0
});
}
}
}
}
initPhysics() {
this.physics.add.overlap(this.lteamPlayers, this.rteamPlayers, this.hitPlayer, null, this);
this.physics.add.overlap(this.lteamPlayers, this.rteamFlags, this.collectFlag, null, this);
this.physics.add.overlap(this.rteamPlayers, this.lteamFlags, this.collectFlag, null, this);
this.physics.add.overlap(this.lteamPlayers, this.lteamTargetZone, this.dropFlag, null, this);
this.physics.add.overlap(this.rteamPlayers, this.rteamTargetZone, this.dropFlag, null, this);
this.physics.add.overlap(this.lteamPlayers, this.lteamPrisonZone, this.freePlayer, null, this);
this.physics.add.overlap(this.rteamPlayers, this.rteamPrisonZone, this.freePlayer, null, this);
}
initTeams() {
// init L Team
this.lteamFlags = this.add.group();
this.lteamPlayers = this.add.group();
this.lteamState.flags.forEach( flag => {
const flagObj = new Flag(this, flag.x, flag.y, "L", true);
this.lteamFlags.add(flagObj);
});
this.lteamState.players.forEach( player => {
const playerObj = new Player(this, player.name, player.x, player.y, "L", this.lteamState.player_sprite_choice, true);
this.lteamPlayers.add(playerObj);
});
// this.lteamTargetZone = this.add.zone(this.lteamState.target[0].x * this.tileSize, this.lteamState.target[0].y * this.tileSize, 3 * this.tileSize, 3 * this.tileSize);
this.lteamTargetZone = this.add.zone(
this.mapX + (this.lteamState.target[0].x * this.tileSize + 1.5 * this.tileSize),
this.mapY + (this.lteamState.target[0].y * this.tileSize + 1.5 * this.tileSize),
3 * this.tileSize, 3 * this.tileSize);
this.physics.add.existing(this.lteamTargetZone);
this.lteamTargetZone.body.setAllowGravity(false);
this.lteamTargetZone.body.setImmovable(true);
// this.lteamPrisonZone = this.add.zone(this.lteamState.prison[0].x * this.tileSize, this.lteamState.prison[0].y * this.tileSize, 3 * this.tileSize, 3 * this.tileSize);
this.lteamPrisonZone = this.add.zone(
this.mapX + (this.lteamState.prison[0].x * this.tileSize + 1.5 * this.tileSize),
this.mapY + (this.lteamState.prison[0].y * this.tileSize + 1.5 * this.tileSize),
3 * this.tileSize, 3 * this.tileSize);
this.physics.add.existing(this.lteamPrisonZone);
this.lteamPrisonZone.body.setAllowGravity(false);
this.lteamPrisonZone.body.setImmovable(true);
// init R Team
this.rteamFlags = this.add.group();
this.rteamPlayers = this.add.group();
this.rteamState.flags.forEach( flag => {
const flagObj = new Flag(this, flag.x, flag.y, "R", true);
this.rteamFlags.add(flagObj);
});
this.rteamState.players.forEach( player => {
const playerObj = new Player(this, player.name, player.x, player.y, "R", this.rteamState.player_sprite_choice, false);
this.rteamPlayers.add(playerObj);
});
this.rteamTargetZone = this.add.zone(
this.mapX + (this.rteamState.target[0].x * this.tileSize + 1.5 * this.tileSize),
this.mapY + (this.rteamState.target[0].y * this.tileSize + 1.5 * this.tileSize),
3 * this.tileSize, 3 * this.tileSize);
this.physics.add.existing(this.rteamTargetZone);
this.rteamTargetZone.body.setAllowGravity(false);
this.rteamTargetZone.body.setImmovable(true);
this.rteamPrisonZone = this.add.zone(
this.mapX + (this.rteamState.prison[0].x * this.tileSize + 1.5 * this.tileSize),
this.mapY + (this.rteamState.prison[0].y * this.tileSize + 1.5 * this.tileSize),
3 * this.tileSize, 3 * this.tileSize);
this.physics.add.existing(this.rteamPrisonZone);
this.rteamPrisonZone.body.setAllowGravity(false);
this.rteamPrisonZone.body.setImmovable(true);
}
initInput() {
this.cursors = this.input.keyboard.createCursorKeys();
this.awsd_keys = this.input.keyboard.addKeys({
up: Phaser.Input.Keyboard.KeyCodes.W,
left: Phaser.Input.Keyboard.KeyCodes.A,
down: Phaser.Input.Keyboard.KeyCodes.S,
right: Phaser.Input.Keyboard.KeyCodes.D
});
// check for spacebar press only once
// this.cursors.space.once('down', (key, event) => {
// this.startGame();
// });
this.cursors.space.on('down', (key, event) => {
this.startOrPauseOrContinue();
});
}
// create tile map data
initMap() {
this.map = this.make.tilemap({ key: ASSETS.tilemapTiledJSON.map.key });
const tileset = this.map.addTilesetImage(ASSETS.spritesheet.tiles.key);
// create background layer, randomly pick the tiles
this.groundLayer = this.map.createBlankLayer('ground', tileset, this.mapX, this.mapY);
for (let y = 0; y < this.mapHeight; y++) {
for (let x = 0; x < this.mapWidth; x++) {
// randomly choose a tile id from this.tiles
// weightedPick favours items earlier in the array
const tileIndex = Phaser.Math.RND.pick(this.backgroundTiles);
this.groundLayer.putTileAt(tileIndex, x, y);
}
}
// create level layer to show game level elements
this.levelLayer = this.map.createBlankLayer('level', tileset, this.mapX, this.mapY);
this.levelLayer.fill(0, 0, 0, this.mapWidth, this.mapHeight);
// show prison tiles
for (let i = 0; i < this.lteamState.prison.length; ++i) {
const prison = this.lteamState.prison[i];
const tile = this.levelLayer.getTileAt(prison.x, prison.y);
tile.index = this.prisonTiles[i];
}
for (let i = 0; i < this.rteamState.prison.length; ++i) {
const prison = this.rteamState.prison[i];
const tile = this.levelLayer.getTileAt(prison.x, prison.y);
tile.index = this.prisonTiles[i];
}
for (let i = 0; i < this.lteamState.target.length; ++i) {
const target = this.lteamState.target[i];
const tile = this.levelLayer.getTileAt(target.x, target.y);
tile.index = this.targetTiles[i];
}
for (let i = 0; i < this.rteamState.target.length; ++i) {
const target = this.rteamState.target[i];
const tile = this.levelLayer.getTileAt(target.x, target.y);
tile.index = this.targetTiles[i];
}
// create wall
for (let i = 0; i < this.walls.length; ++i) {
const wall = this.walls[i];
const tile = this.levelLayer.getTileAt(wall.x, wall.y);
tile.index = wall.tileId;
const collisionId = (wall.x - 1) * this.mapWidth + wall.y;
this.map.setCollision(collisionId);
}
// create obstacles
for (let i = 0; i < this.obstacles1.length; ++i) {
const obs = this.obstacles1[i];
const tile = this.levelLayer.getTileAt(obs.x, obs.y);
tile.index = Phaser.Math.RND.pick(this.tree1Tiles);
const collisionId = (obs.x - 1) * this.mapWidth + obs.y;
this.map.setCollision(collisionId);
}
for (let i = 0; i < this.obstacles2.length; ++i) {
const obs = this.obstacles2[i];
const treeTile = Phaser.Math.RND.pick(this.tree2Tiles);
const tile1 = this.levelLayer.getTileAt(obs.x, obs.y);
tile1.index = treeTile[0];
const tile2 = this.levelLayer.getTileAt(obs.x, obs.y + 1);
tile2.index = treeTile[1];
const collisionId = (obs.x - 1) * this.mapWidth + obs.y;
this.map.setCollision(collisionId, collisionId + 1);
}
}
// create a thin black line in the middle
initBoundary() {
const startY = this.centerY - this.mapHeight * this.tileSize / 2;
const endY = this.centerY + this.mapHeight * this.tileSize / 2;
this.add.line(0, 0, this.centerX, startY, this.centerX, endY, 0x000000)
.setOrigin(0, 0)
.setLineWidth(1);
}
startGame() {
this.gameStarted = true;
this.tutorialText.setVisible(false);
const mapPayload = {
"width": this.mapWidth,
"height": this.mapHeight,
"walls": this.walls.map(w => {return {x: w.x, y: w.y}}),
"obstacles": this.obstacles1.concat(this.obstacles2).concat(
this.obstacles2.map(w => {return {"x": w.x, "y": w.y + 1}})
),
};
if (this.lteamSocket && this.lteamSocket.readyState == WebSocket.OPEN) {
const payload = {
"action": "init",
"map": mapPayload,
"numPlayers": this.NUM_PLAYERS,
"numFlags": this.NUM_FLAGS,
"myteamName": "L",
// this is where you will be sent to, if you were caught by opponent
"myteamPrison": this.lteamState.prison,
// this is where you will drop the flags
"myteamTarget": this.lteamState.target,
"opponentPrison": this.rteamState.prison,
"opponentTarget": this.rteamState.target,
}
this.lteamSocket.send(JSON.stringify(payload));
}
if (this.rteamSocket && this.rteamSocket.readyState == WebSocket.OPEN) {
const payload = {
"action": "init",
"map": mapPayload,
"numPlayers": this.NUM_PLAYERS,
"numFlags": this.NUM_FLAGS,
"myteamName": "R",
// this is where you will send the opponent to prison
"myteamPrison": this.rteamState.prison,
// this is where you will drop the flags
"myteamTarget": this.rteamState.target,
"opponentPrison": this.lteamState.prison,
"opponentTarget": this.lteamState.target,
}
this.rteamSocket.send(JSON.stringify(payload));
}
}
hitPlayer(player1, player2) {
// does not matter if player1 is not in the same team as player2
if (player1.team === player2.team) {
return;
}
if (player1.inPrison || player2.inPrison) {
return;
}
// When collision happens around the center, we use the middle X
let playerCenterX = (player1.x + player2.x) / 2;
// in L team's side
if (playerCenterX < this.centerX) {
// find the prison tile to send the R team player
// If the flag is held by the R team player, drop the flag at where it was caught.
const spot = this.findAvailablePrisonTile(this.rteamPlayers.getChildren(), this.rteamState.prison);
const caughtPlayer = player1.team === "R" ? player1 : player2;
if (caughtPlayer.hasFlag) {
const tile = this.getTileAt(caughtPlayer.x, caughtPlayer.y);
const flag = new Flag(this, tile.x, tile.y, "L", true);
this.lteamFlags.add(flag);
caughtPlayer.hasFlag = false;
}
caughtPlayer.toPrison(spot.x, spot.y);
} else {
const spot = this.findAvailablePrisonTile(this.lteamPlayers.getChildren(), this.lteamState.prison);
const caughtPlayer = player1.team === "L" ? player1 : player2;
if (caughtPlayer.hasFlag) {
const tile = this.getTileAt(caughtPlayer.x, caughtPlayer.y);
const flag = new Flag(this, tile.x, tile.y, "R", true);
this.rteamFlags.add(flag);
caughtPlayer.hasFlag = false;
}
caughtPlayer.toPrison(spot.x, spot.y);
}
}
findAvailablePrisonTile(players, prisons) {
for (let i = 0; i < prisons.length; ++i) {
let isAvailable = true;
for (let j = 0; j < players.length; ++j) {
if (!players[j].inPrison) {
continue;
}
const tile = this.getTileAt(players[j].x, players[j].y);
if (tile.x == prisons[i].x && tile.y == prisons[i].y) {
isAvailable = false;
break;
}
}
if (isAvailable) {
return {x: prisons[i].x, y: prisons[i].y};
}
}
}
dropFlag(player) {
if (!player.hasFlag) {
return;
}
player.dropFlag();
if (player.team == "L") {
const spot = this.findAvailableFlagTile(this.rteamFlags.getChildren(), this.lteamState.target);
const flag = new Flag(this, spot.x, spot.y, "R", false);
this.rteamFlags.add(flag);
this.updateTeamScore("L");
} else {
const spot = this.findAvailableFlagTile(this.lteamFlags.getChildren(), this.rteamState.target);
const flag = new Flag(this, spot.x, spot.y, "L", false);
this.lteamFlags.add(flag);
this.updateTeamScore("R");
}
}
findAvailableFlagTile(flags, targets) {
for (let i = 0; i < targets.length; ++i) {
let isAvailable = true;
for (let j = 0; j < flags.length; ++j) {
if (flags[j].canPickup) {
continue;
}
const tile = this.getTileAt(flags[j].x, flags[j].y);
if (tile.x == targets[i].x && tile.y == targets[i].y) {
isAvailable = false;
break;
}
}
if (isAvailable) {
return {x: targets[i].x, y: targets[i].y};
}
}
}
collectFlag(player, flag) {
// cannot collect flag from my team
if (player.team == flag.team) {
return;
}
if (player.inPrison) {
return;
}
// a player cannot collect >1 flag
if (player.hasFlag) {
return;
}
// cannot collect flag that cannot be collected
if (!flag.canPickup) {
return;
}
flag.collect();
player.collectFlag();
}
removeFlagItem(flag) {
if (flag.team == "L") {
this.lteamFlags.remove(flag, true, true);
} else if (flag.team == "R") {
this.rteamFlags.remove(flag, true, true);
}
}
freePlayer(player) {
// player sent to prison cannot free others
if (player.inPrison) {
return;
}
if (player.team == "L") {
this.lteamPlayers.getChildren().forEach( player => {
if (player.inPrison) { player.inPrison = false; }
})
} else {
this.rteamPlayers.getChildren().forEach( player => {
if (player.inPrison) { player.inPrison = false; }
})
}
}
updateTeamScore(team) {
if (team == "L") {
++this.lteamState.score;
this.lScoreText.setText(`LTeam #Flags: ${this.lteamState.score}`);
if (this.lteamState.score == this.NUM_FLAGS) {
this.GameOver(team);
}
} else if (team == "R") {
++this.rteamState.score;
this.rScoreText.setText(`RTeam #Flags: ${this.rteamState.score}`);
if (this.rteamState.score == this.NUM_FLAGS) {
this.GameOver(team);
}
}
}
getMapOffset() {
return {
x: this.mapX + this.halfTileSize,
y: this.mapY + this.halfTileSize,
width: this.mapWidth,
height: this.mapHeight,
tileSize: this.tileSize
}
}
getTileAt(x, y) {
const tile = this.levelLayer.getTileAtWorldXY(x, y, true);
return tile;
}
isWall(x, y) {
const tile = this.levelLayer.getTileAtWorldXY(x, y, true);
return this.wallTiles.indexOf(tile.index) >= 0 ||
this.tree1Tiles.indexOf(tile.index) >= 0 ||
this.tree2Tiles[0].indexOf(tile.index) >= 0 ||
this.tree2Tiles[1].indexOf(tile.index) >= 0
;
}
// return a 3x3 grid from x, y
create3x3grid(x, y) {
return [
{x: x - 1, y: y - 1}, {x: x, y: y - 1}, {x: x + 1, y: y - 1},
{x: x - 1, y: y}, {x: x, y: y}, {x: x + 1, y: y},
{x: x - 1, y: y + 1}, {x: x, y: y + 1}, {x: x + 1, y: y + 1},
]
}
GameOver(team) {
this.gameStarted = false;
this.gameOverText.setText(team+"Team Won!")
this.gameOverText.setVisible(true);
if (this.lteamSocket && this.lteamSocket.readyState == WebSocket.OPEN) {
const payload = {
action: "finished",
myteamScore: this.lteamState.score,
opponentScore: this.rteamState.score,
}
this.lteamSocket.send(JSON.stringify(payload));
}
if (this.rteamSocket && this.rteamSocket.readyState == WebSocket.OPEN) {
const payload = {
action: "finished",
myteamScore: this.rteamState.score,
opponentScore: this.lteamState.score,
}
this.rteamSocket.send(JSON.stringify(payload));
}
}
}
+16
View File
@@ -0,0 +1,16 @@
export class GameOver extends Phaser.Scene {
constructor() {
super('GameOver');
}
create() {
this.background1 = this.add.image(0, 0, 'background').setOrigin(0);
this.add.text(this.scale.width * 0.5, this.scale.height * 0.5, 'Game Over', {
fontFamily: 'Arial Black', fontSize: 64, color: '#ffffff',
stroke: '#000000', strokeThickness: 8,
align: 'center'
}).setOrigin(0.5);
}
}
+49
View File
@@ -0,0 +1,49 @@
import ASSETS from '../assets.js';
export class Preloader extends Phaser.Scene {
constructor() {
super('Preloader');
}
init() {
const centreX = this.scale.width * 0.5;
const centreY = this.scale.height * 0.5;
const barWidth = 468;
const barHeight = 32;
const barMargin = 4;
// We loaded this image in our Boot Scene, so we can display it here
// A simple progress bar. This is the outline of the bar.
this.add.rectangle(centreX, centreY, barWidth, barHeight).setStrokeStyle(1, 0xffffff);
// This is the progress bar itself. It will increase in size from the left based on the % of progress.
const bar = this.add.rectangle(centreX - (barWidth * 0.5) + barMargin, centreY, barMargin, barHeight - barMargin, 0xffffff);
// Use the 'progress' event emitted by the LoaderPlugin to update the loading bar
this.load.on('progress', (progress) => {
// Update the progress bar (our bar is 464px wide, so 100% = 464px)
bar.width = barMargin + ((barWidth - (barMargin * 2)) * progress);
});
}
preload() {
// Load the assets for the game - see ./src/assets.js
for (let type in ASSETS) {
for (let key in ASSETS[type]) {
let args = ASSETS[type][key].args.slice();
args.unshift(ASSETS[type][key].key);
this.load[type].apply(this.load, args);
}
}
}
create() {
// When all the assets have loaded, it's often worth creating global objects here that the rest of the game can use.
// For example, you can define global animations here, so we can use them in other scenes.
// Move to the MainMenu. You could also swap this for a Scene Transition, such as a camera fade.
this.scene.start('Game');
}
}
+1
View File
@@ -0,0 +1 @@
python3 -m http.server ${CTF_PORT_FRONTEND} --bind 0.0.0.0