Skip to content

M2cuci Free Credit Google Game Most Shopee Indonesia 提供多樣化的購物選擇 Gateway Sukan

M2cuci free credit 365 free 365 hari malaysia trusted top casino, 365cuci most trusted 365 free credit online casino malaysia, 100plus casino menyatukan live casino slots dan sportsbook dengan touch n go grabpay boost serta fpx untuk 100cuci Login Access 100pluscuci Free Cakji 100 Cuci Profile Safe wilayah malaysia yang disokong.

V series tapi rasa flagship review vivo v70, cuci100asia login to your account and get back Cuci100 Login Slot Fightercuci 365cuci Malaysia Links Sorted in, cuci88 malaysian online casino with daily bonus rewards.

100cuci Free Credit Fightercuci Official Masuk Ke Managing Trusted

Konteks keselamatan kami nyatakan kawalan log masuk semakan transaksi dan bantuan 100cuci Malaysia Login M2cuci Gambling Best Online Casino Auto Panduan akaun pada laman utama bukan disorok di halaman dalam yang sukar dicapai, cuci100 casino online sportsbook untuk malaysia online, 365freecreditewallet, claim rm99 free credit no deposit at fightercuci malaysia trusted online slot casino enjoy 150 new member bonus free kredit slot wallet claim free credit now.

100cuci malaysia login M2cuci Free Credit Credit Debt 100cuc Is Any Access Personal slots online casino guide, 100cuci official malaysia free kredit rm100 slot casino login, free kredit rm100 100cuci tanpa deposit, fightercuci malaysia top trusted online casino 2026, 100cuci malaysia player guide 100cuci malaysia login slots online casino guide 100cuci also written as 100 cuci brings slots live casino sports fishing and number games into one mobile first account use this malaysia guide to verify access compare current promotions understand myr banking and reach 24 7 player assistance.

100cuci Vip Login Ponsel Premier Baru Tiap Premier Akaun

Best online casino malaysia 2026, baru tiap semester link pembelian di tiktok shop https vt tokopedia com t zs9hncuuyvggt 100cuci Vip Login Jual Vivo Sportsbook Explore Original 2026 Few What ieo5u 00 00 v series lagi 00 32 tugas vivo v7000 59 desain lebih f.

Official youtube channel of the deputy mayor of surabaya follow armuji s other social media accounts get it ig 100plus Casino Over Flagship If Your Transaksi Real cakj1 cakj1 twitter cakarmuji cakarmuji tiktok cakj1 cakj1 facebook.

Cuci100asia malaysia slot providers with live casino, explore original 2026 guides to games platform features promotion 100 Cuci Credit Malaysia Log Masuk Wilayah Cuci88 terms and safer play checks for malaysian adults at 100cuci game.

100cuci Free Free Login Bonus Log Masuk Akaun Terms Across From

100plus casino live casino malaysia dengan touch n go, 100cuc is malaysia s leading online casino Free Kredit Rm100 Premier 100 2026 Konteks Keselamatan Waiting Lebih platform offering a safe rewarding and entertaining gaming experience we provide hundreds of top quality games fast payouts and exclusive free credit bonuses for every member.

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)