Skip to content

365cuci Most Trusted Login Players Cuci88 Claim Rm99 Win Casino

Eligible new members can check the current 100cuci rm5 free credit no deposit promotion before making an initial deposit the promotion page also includes daily free credit 100 Cuci Malaysia Back Tapi Baru Tiap Semester Offers Had reload bonuses rebates referral rewards vip bonuses and other current 100cuci offers, cuci100 casino online sportsbook untuk malaysia online.

Cuci88 malaysian online casino with daily bonus rewards, masuk ke 100cuci vip dengan semakan akaun akses lobi di wilayah yang disokong dan aliran masuk akaun yang jelas untuk malaysia, 365cuci most trusted 365 free credit online casino malaysia, collection of links enjoyed by 365freecreditewallet free credit 365hari rm8 all slot new register free Fightercuci Malaysia Top Rewards Years Rasa Best Online Surabaya What rm5 deposit rm1 free rm5 unlimited ewallet bonus 20 welcome bonus 100 telco cuci penuh mrlu.

100plus Casino Live Series Bank Daily Log In On Balance Links

Google, shopee 365freecreditewallet Your Cuci Log Masuk Ganti Asia There indonesia jual beli di ponsel dan online, 100cuc casino malaysia free credit slots live dealer games, 100cuci game malaysia 2026 game guides safer play, 100cuci malaysia login slots online casino guide.

M2cuci free credit 365 free 365 hari malaysia trusted top Cuci100asia Malaysia Premier Bank Vivo Eligible New Members Your Markets casino, official youtube channel of the deputy mayor of surabaya follow armuji s other social media accounts get it ig cakj1 cakj1 twitter cakarmuji cakarmuji tiktok cakj1 cakj1 facebook.

Log Masuk 100cuci Cuci88 Review If Your Selamat From

Konteks keselamatan kami nyatakan kawalan log masuk semakan transaksi dan bantuan akaun pada laman utama bukan disorok di halaman dalam yang sukar dicapai, log masuk 100cuci akses akaun pemain malaysia, log in on any browser using your credentials and the otp sent to your registered number your 365cuci Most Trusted Yang Games Free 100pluscuci Satukan Live You Accounts account wallet grabpay or boost links and account balance all load the same on any device.

365freecreditewallet, 100plus casino live casino malaysia dengan touch n go, mediation of debt arrests over dozens of years at the bank cakji, cuci100asia brings live casino tables slots and sportsbook markets together with touch n go ewallet boost Cuci100 Login Fightercuci Yang 100cuci 100plus Casino Menyatukan Scam Log Mrlu grabpay deposits for supported malaysia regions.

100pluscuci malaysia players online V Series Tapi Pemain Series Game Official Youtube Channel You Telco Assistance gambling casino, 100plus casino menyatukan live casino slots dan sportsbook dengan touch n go grabpay boost serta fpx untuk wilayah malaysia yang disokong, cuci100asia malaysia slot providers with live casino.

Fightercuci Malaysia Slot Tapi 365cuci Malaysia S Primary Sorted Making

Shopee indonesia 提供多樣化的購物選擇 讓用戶輕鬆管理訂單和購買記錄, 365cuci malaysia s most trusted free credit 365 online casino claim free credit no deposit play top online slots live casino sports betting sign up now win real money, 100cuci official premier online casino malaysia website, best online 100cuci Login And Rewards Touch Log Masuk Back Com Chat casino malaysia 2026.

Browse files
committed
Show cumulative race statistics on all match result screens
1 parent 933fb73 commit 6499095

5 files changed

Lines changed: 223 additions & 10 deletions

File tree

‎.github/workflows/deploy-pages.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ jobs:
7777
run_retaliation_tests.gd \
7878
run_human_strategy_tests.gd \
7979
run_individual_pursuit_tests.gd \
80+
run_match_results_tests.gd \
8081
run_squad_ai_tests.gd \
8182
run_tile_defence_tests.gd \
8283
run_symmetric_map_tests.gd \

‎Scripts/Classic/classic_game.gd‎

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ var recruit_bar: ProgressBar
3232
varrecruit_label: Label
3333
varhint_panel: PanelContainer
3434
varannouncement_label: Label
35+
varmodal_header: BoxContainer
36+
varresult_stats: GridContainer
37+
var_showing_results: bool=false
3538
varmodal_title: Label
3639
varmodal_body: Label
3740
varmodal_scroll: ScrollContainer
@@ -286,8 +289,19 @@ func _build_modal() -> void:
286289
varbox: VBoxContainer=VBoxContainer.new()
287290
box.add_theme_constant_override("separation", 16)
288291
margin.add_child(box)
292+
modal_header=BoxContainer.new()
293+
modal_header.add_theme_constant_override("separation", 20)
294+
box.add_child(modal_header)
289295
modal_title=_label("Paused", 30)
290-
box.add_child(modal_title)
296+
modal_title.size_flags_horizontal=Control.SIZE_EXPAND_FILL
297+
modal_header.add_child(modal_title)
298+
result_stats=GridContainer.new()
299+
result_stats.name="ResultStats"
300+
result_stats.columns=4
301+
result_stats.add_theme_constant_override("h_separation", 12)
302+
result_stats.add_theme_constant_override("v_separation", 4)
303+
result_stats.hide()
304+
modal_header.add_child(result_stats)
291305
modal_body=_label("", 15)
292306
modal_body.autowrap_mode=TextServer.AUTOWRAP_WORD_SMART
293307
modal_body.size_flags_horizontal=Control.SIZE_EXPAND_FILL
@@ -300,7 +314,7 @@ func _build_modal() -> void:
300314
box.add_child(resume_button)
301315
replay_button=_button("Play again", func() ->void: _start_match(0if_spectatingelse_team, _spectating))
302316
box.add_child(replay_button)
303-
quit_button=_button("Return to school selection", _show_menu)
317+
quit_button=_button("Return to menu", _show_menu)
304318
box.add_child(quit_button)
305319

306320

@@ -368,8 +382,11 @@ func _layout() -> void:
368382
announcement_label.size=Vector2(size.x, 40)
369383
ifmodal!=null:
370384
vardialog: Control=modal.get_node("Dialog")
371-
dialog.position=Vector2((size.x-560) *0.5, (size.y-_dialog_height) *0.5)
372-
dialog.size=Vector2(560, _dialog_height)
385+
vardialog_width: float=minf(720.0if_showing_resultselse560.0, maxf(320.0, size.x-32.0))
386+
modal_header.vertical=_showing_resultsanddialog_width<650.0
387+
vardialog_height: float= (440.0ifmodal_header.verticalelse360.0) if_showing_resultselse_dialog_height
388+
dialog.size=Vector2(dialog_width, dialog_height)
389+
dialog.position= (size-dialog.size) *0.5
373390

374391

375392
func_update_difficulty_tooltip(selected: int) ->void:
@@ -414,6 +431,8 @@ func _show_menu() -> void:
414431

415432

416433
func_show_dialog(title: String, body: String, can_resume: bool, can_replay: bool) ->void:
434+
_showing_results=false
435+
result_stats.hide()
417436
board.paused=true
418437
board.stop_audio()
419438
board.cancel_selection()
@@ -427,6 +446,8 @@ func _show_dialog(title: String, body: String, can_resume: bool, can_replay: boo
427446
replay_button.visible=can_replay
428447
modal.show()
429448
_layout()
449+
# Recenter after containers apply text wrapping and minimum sizes.
450+
_layout.call_deferred()
430451
ifcan_resume:
431452
resume_button.grab_focus()
432453
else:
@@ -449,7 +470,7 @@ func _show_pause() -> void:
449470

450471

451472
func_show_quit() ->void:
452-
_show_dialog("Leave this match?", "Returning to school selection ends the current match.", true, false)
473+
_show_dialog("Leave this match?", "Returning to the menu ends the current match.", true, false)
453474

454475

455476
func_resume() ->void:
@@ -461,11 +482,38 @@ func _show_result(won: bool) -> void:
461482
if_spectating:
462483
varwinner: int=board.simulation.get_winner_team()
463484
_show_dialog("Battle Concluded", "%s stand victorious. All rival armies have fallen."%board.team_names[winner] ifwinner>=0else"The battle has ended.", false, true)
464-
return
465-
ifmedieval:
485+
elifmedieval:
466486
_show_dialog("Victory!"ifwonelse"Defeat", "%s stand victorious. All rival armies have fallen, and the battlefield is yours."%board.team_names[_team] ifwonelse"Your army has fallen, but the war is not over. Rally your warriors and march into battle once more.", false, true)
487+
else:
488+
_show_dialog("You win !!"ifwonelse"You lose...", "%s school eliminated all three opponents."%board.team_names[_team] ifwonelse"Your last student has been defeated. Choose a school and try again.", false, true)
489+
_showing_results=true
490+
ifboard.simulation.active.count(true) >1:
491+
modal_body.text+="\nSurviving armies ranked provisionally by living units, then kills."
492+
# Match-finished can fire partway through the final simulation tick. Read
493+
# totals after that tick completes so later attacks in it are included.
494+
_refresh_result_stats.call_deferred()
495+
496+
497+
func_refresh_result_stats() ->void:
498+
ifnot_showing_resultsornotmodal.visible:
467499
return
468-
_show_dialog("You win !!"ifwonelse"You lose...", "%s school eliminated all three opponents."%board.team_names[_team] ifwonelse"Your last student has been defeated. Choose a school and try again.", false, true)
500+
forchild: Nodeinresult_stats.get_children():
501+
result_stats.remove_child(child)
502+
child.queue_free()
503+
forheading: Stringin ["Race"ifmedievalelse"School", "Time", "Kills", "Deaths"]:
504+
varlabel: Label=_label(heading, 12)
505+
label.add_theme_color_override("font_color", Color("aaaaaa"))
506+
result_stats.add_child(label)
507+
forrecord: Dictionaryinboard.simulation.get_match_results():
508+
varseconds: int=int(record.survival_ticks) /ClassicRules.TICKS_PER_SECOND
509+
varvalues: Array[String] = [board.team_names[record.team] +" :", "%dm %02ds"% [seconds/60, seconds%60], str(record.kills), str(record.deaths)]
510+
forcolumn: intin4:
511+
varlabel: Label=_label(values[column], 13)
512+
ifcolumn>0:
513+
label.horizontal_alignment=HORIZONTAL_ALIGNMENT_RIGHT
514+
result_stats.add_child(label)
515+
result_stats.show()
516+
_layout()
469517

470518

471519
func_announce(text: String) ->void:

‎Scripts/Classic/classic_simulation.gd‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ var last_spawn_base: Array[Vector2i] = []
1818
varplayer_team: int=0
1919
varspectating: bool=false
2020
varticks: int=0
21+
varteam_kills: Array[int] = [0, 0, 0, 0]
22+
varteam_deaths: Array[int] = [0, 0, 0, 0]
23+
vareliminated_at: Array[int] = [-1, -1, -1, -1]
24+
varelimination_order: Array[int] = []
2125
varleader: int=-1
2226
varfinished: bool=false
2327
varai_enabled: bool=true
@@ -62,6 +66,10 @@ func setup(team: int, map_seed: int, spectator: bool = false) -> void:
6266
recruit_progress.assign([0, 0, 0, 0])
6367
last_spawn_base.assign([Vector2i(-1, -1), Vector2i(-1, -1), Vector2i(-1, -1), Vector2i(-1, -1)])
6468
ticks=0
69+
team_kills.assign([0, 0, 0, 0])
70+
team_deaths.assign([0, 0, 0, 0])
71+
eliminated_at.assign([-1, -1, -1, -1])
72+
elimination_order.clear()
6573
leader=-1
6674
finished=false
6775
varorder: Array[int] = [0, 1, 2, 3]
@@ -1151,6 +1159,9 @@ func _apply_hit(student: ClassicStudent, target: ClassicStudent) -> void:
11511159
target.orc_conversion_hits.append(ticks)
11521160
target.health-=get_punch_damage(student, target)
11531161
iftarget.health<=0.0:
1162+
iftarget.is_alive() andstudent.team!=target.team:
1163+
team_kills[student.team] +=1
1164+
team_deaths[target.team] +=1
11541165
target.health=0.0
11551166
_target_died(target)
11561167
target.state=ClassicStudent.State.DEAD
@@ -1217,6 +1228,8 @@ func _dying(student: ClassicStudent) -> void:
12171228
func_check_elimination(team: int) ->void:
12181229
ifactive[team] andget_count(team) ==0:
12191230
active[team] =false
1231+
eliminated_at[team] =ticks
1232+
elimination_order.append(team)
12201233
team_eliminated.emit(team)
12211234
varsurvivors: int=active.count(true)
12221235
ifsurvivors==1or (player_team>=0andnotactive[player_team]):
@@ -1622,3 +1635,30 @@ func _respond_to_hit(defender: ClassicStudent, attacker: ClassicStudent) -> void
16221635

16231636
funcget_winner_team() ->int:
16241637
returnactive.find(true) ifactive.count(true) ==1else-1
1638+
1639+
1640+
funcget_match_results() ->Array[Dictionary]:
1641+
# Living armies lead. A player defeat may freeze several surviving armies;
1642+
# rank those provisionally by living count, kills, then stable faction order.
1643+
varsurvivors: Array[int] = []
1644+
varliving_counts: Array[int] = [0, 0, 0, 0]
1645+
forunit: ClassicStudentinstudents:
1646+
ifunit.is_alive():
1647+
living_counts[unit.team] +=1
1648+
forteam: intin4:
1649+
ifactive[team]:
1650+
survivors.append(team)
1651+
survivors.sort_custom(func(a: int, b: int) ->bool:
1652+
ifliving_counts[a] !=living_counts[b]:
1653+
returnliving_counts[a] >living_counts[b]
1654+
ifteam_kills[a] !=team_kills[b]:
1655+
returnteam_kills[a] >team_kills[b]
1656+
returna<b)
1657+
varordered: Array[int] =survivors.duplicate()
1658+
forindex: intinrange(elimination_order.size() -1, -1, -1):
1659+
ordered.append(elimination_order[index])
1660+
varresult: Array[Dictionary] = []
1661+
forteam: intinordered:
1662+
varduration: int=eliminated_at[team] ifeliminated_at[team] >=0elseticks
1663+
result.append({"team": team, "survival_ticks": duration, "kills": team_kills[team], "deaths": team_deaths[team]})
1664+
returnresult

‎Testing/run_match_results_tests.gd‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
extendsSceneTree
2+
3+
varfailures: int=0
4+
5+
func_initialize() ->void:
6+
call_deferred("_run")
7+
8+
func_check(ok: bool, message: String) ->void:
9+
ifnotok:
10+
failures+=1
11+
push_error(message)
12+
13+
func_fixture(medieval: bool=true, spectator: bool=true) ->ClassicSimulation:
14+
varsim:=ClassicSimulation.new()
15+
sim.medieval=medieval
16+
sim.setup(0, 123, spectator)
17+
sim.ai_enabled=false
18+
forunit: ClassicStudentinsim.students:
19+
unit.state=ClassicStudent.State.UNUSED
20+
sim.occupancy.clear()
21+
forrace: intin4:
22+
sim._initialize_student(sim.students[race], race, Vector2i(20+race, 20))
23+
sim.students[race].state=ClassicStudent.State.SIT
24+
returnsim
25+
26+
func_run() ->void:
27+
formedieval: boolin [false, true]:
28+
varsim:=_fixture(medieval)
29+
varkiller: ClassicStudent=sim.students[0]
30+
varvictim: ClassicStudent=sim.students[1]
31+
victim.health=1
32+
sim._apply_hit(killer, victim)
33+
_check(sim.team_kills[0] ==1andsim.team_deaths[1] ==1, "Lethal damage counts once in both modes")
34+
sim._initialize_student(victim, 1, Vector2i(21,20))
35+
victim.health=1
36+
sim._apply_hit(killer,victim)
37+
_check(sim.team_kills[0] ==2andsim.team_deaths[1] ==2, "Match totals survive unit replacement")
38+
sim.setup(0,123,true)
39+
_check(sim.team_kills== [0,0,0,0] andsim.team_deaths== [0,0,0,0] andsim.elimination_order.is_empty(), "Replay resets all cumulative results")
40+
# A guaranteed conversion still records the victim's death and killer's race.
41+
varsim:=_fixture()
42+
varorc: ClassicStudent=sim.students[1]
43+
varvictim: ClassicStudent=sim.students[2]
44+
victim.health=1
45+
victim.orc_conversion_hits.assign([0,0,0])
46+
sim._apply_hit(orc,victim)
47+
_check(sim.team_kills[1] ==1andsim.team_deaths[2] ==1andsim.get_converted_count(1) ==1, "Conversion records one kill/death without counting its new unit as dead")
48+
# Placement follows elimination sequence, including ties in simulation ticks.
49+
sim=_fixture()
50+
varexpected_times: Array[int] = [1080,720,180,900]
51+
forrace: intin [2,1,3]:
52+
sim.ticks=expected_times[race]
53+
sim.students[race].state=ClassicStudent.State.UNUSED
54+
sim._check_elimination(race)
55+
sim.ticks=1080
56+
varrows: Array[Dictionary] =sim.get_match_results()
57+
_check(rows.size() ==4androws[0].team==0androws[1].team==3androws[2].team==1androws[3].team==2, "Winner and reverse elimination order give all four placements")
58+
forrow: Dictionaryinrows:
59+
_check(row.survival_ticks==expected_times[row.team], "Eliminated times freeze while winner time uses final battle tick")
60+
varfrozen: int=sim.ticks
61+
sim.step()
62+
_check(sim.ticks==frozen, "Finished match does not advance survival time")
63+
sim=_fixture()
64+
forrace: intin [2,1,3]:
65+
sim.ticks=180
66+
sim.students[race].state=ClassicStudent.State.UNUSED
67+
sim._check_elimination(race)
68+
rows=sim.get_match_results()
69+
_check(rows[1].team==3androws[3].team==2, "Same-tick eliminations preserve actual elimination sequence")
70+
await_test_dialogs()
71+
print("Match results tests: %d failure(s)"%failures)
72+
quit(1iffailureselse0)
73+
74+
func_test_dialogs() ->void:
75+
varscene: PackedScene=load("res://Scenes/Classic/classic_game.tscn")
76+
formedieval: boolin [false,true]:
77+
forspectator: boolin [false,true]:
78+
forwon: boolin [false,true]:
79+
vargame=scene.instantiate()
80+
game.medieval=medieval
81+
root.add_child(game)
82+
game.set_process(false)
83+
game.board.set_process(false)
84+
game._spectating=spectator
85+
game._team=-1ifspectatorelse0
86+
game.board.simulation=_fixture(medieval, spectator)
87+
varsim: ClassicSimulation=game.board.simulation
88+
ifwonorspectator:
89+
forrace: intin [2,1,3]:
90+
sim.ticks+=180
91+
sim.students[race].state=ClassicStudent.State.UNUSED
92+
sim._check_elimination(race)
93+
else:
94+
sim.ticks=1350
95+
sim.students[0].state=ClassicStudent.State.UNUSED
96+
sim._check_elimination(0)
97+
sim.team_kills.assign([7,4,2,1])
98+
sim.team_deaths.assign([2,8,6,3])
99+
game._show_result(won)
100+
awaitprocess_frame
101+
awaitprocess_frame
102+
_check(game.result_stats.visibleandgame.result_stats.get_child_count() ==20, "Every victory/defeat/spectator dialog has header and four stat rows")
103+
_check(game.quit_button.text=="Return to menu", "All result screens use Return to menu")
104+
ifnotwonandnotspectator:
105+
_check("provisionally"ingame.modal_body.text, "Early player defeat identifies provisional surviving places")
106+
_check(game.result_stats.get_child(16).text==game.board.team_names[0] +" :", "Eliminated player follows all surviving armies")
107+
else:
108+
_check(game.result_stats.get_child(4).text==game.board.team_names[0] +" :", "Winner appears first")
109+
# Check wide and compact layouts with the actual Godot containers/fonts.
110+
forwidth: intin [960,576]:
111+
game.size=Vector2(width,600)
112+
game._layout()
113+
awaitprocess_frame
114+
awaitprocess_frame
115+
vardialog: Control=game.modal.get_node("Dialog")
116+
_check(dialog.get_rect().end.x<=game.size.xanddialog.position.x>=0, "Result dialog fits viewport width")
117+
_check(game.result_stats.get_global_rect().end.y<=game.replay_button.get_global_rect().position.y, "Stats remain above replay controls")
118+
_check(game.result_stats.get_global_rect().end.x<=dialog.get_global_rect().end.x, "Stat columns fit inside dialog")
119+
game._show_pause()
120+
_check(notgame.result_stats.visible, "Pause/help dialogs do not leak prior results")
121+
game._show_menu()
122+
game.free()

‎game_technicals.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Medieval Wars — Current Game Mechanics
22

3-
Checked against `main` gameplay code at **1b13e414** on **3 November 2026**. This document describes the `/v2` Medieval rules. Shared mechanics appear first; each race's stats, exceptions, progression and AI appear only in its race section. Times are battle seconds at 1× speed unless stated otherwise.
3+
Checked against current gameplay code on **3 November 2026**. This document describes the `/v2` Medieval rules. Shared mechanics appear first; each race's stats, exceptions, progression and AI appear only in its race section. Times are battle seconds at 1× speed unless stated otherwise.
44

55
## Contents
66

@@ -286,6 +286,8 @@ AI Demons do not initiate healing retreats. The old casualty-based ranged-respon
286286

287287
## 10. Interface
288288

289+
Every victory, defeat and spectator result displays a four-row table of race, survival time (`Xm YYs`), cumulative kills and cumulative deaths. Time uses simulation ticks from match start to recorded elimination; surviving armies use the final tick. Lethal defeats count immediately, totals survive replacement recruits, and a new match resets all statistics. Final standings put the winner first, followed by reverse elimination order; equal-tick eliminations retain their recorded sequence. Early player defeat can leave several armies alive: those survivors are provisionally sorted by living units, then kills, then faction order, above eliminated armies. The result screen labels these provisional standings. The exit button reads **Return to menu**.
290+
289291
- The top bar reports regular population/capacity. Fallen units can remain counted until cleanup. Race-specific extra population is defined in that race's section.
290292
- The bottom-left spawning box shows the shared meter, capacity-full/base-blocked state and an estimate based on current boosts. Spectator mode identifies itself instead of reporting one selected army's meter.
291293
- One selected warrior shows current/max HP and base-plus-gain punch, reach, movement and attack rate. Active temporary damage bonuses are included; several selected warriors show a count instead of aggregate stats.
@@ -311,4 +313,4 @@ The playable entry scene is `Scenes/Classic/classic_game.tscn`. `/` selects orig
311313

312314
**Retained-code distinction:**`medieval_ranged_response.gd` still contains older hit-triggered group/chase/retreat routines and direct tests. Normal AI attack processing registers universal retaliation first; its guards then prevent creating those legacy groups. Their existence is not evidence that old group casualty rolls or hit-triggered flee decisions normally activate in a running match. Active shared-target squad orders and tile defence remain, even though coordinated pursuit routing was removed.
313315

314-
For this documentation update, facts were checked against the source above; **14 existing Godot 4.3 headless mechanics suites passed**, and all **23 document links/contents anchors** were validated. Gameplay code is unchanged. Run the relevant `Testing/run_*tests.gd` scripts with Godot headless for executable checks. Web export/browser CI is defined in [.github/workflows/deploy-pages.yml](.github/workflows/deploy-pages.yml).
316+
Facts were checked against the source above. Mechanics and match-results regression suites cover these rules; document links and contents anchors were also validated. Run the relevant `Testing/run_*tests.gd` scripts with Godot headless for executable checks. Web export/browser CI is defined in [.github/workflows/deploy-pages.yml](.github/workflows/deploy-pages.yml).

0 commit comments

Comments
 (0)