EZTIME2025 commited on
Commit
81eb3d4
·
1 Parent(s): 548394f

trying fix knignt

Browse files
.github/instructions/BUILD_PLAN.md CHANGED
@@ -179,7 +179,7 @@
179
  ### משימה 5.3: Development Cards & Special Actions
180
  **סטטוס:** 🟢 בעבודה פעילה!
181
  **תאריך התחלה:** 10 דצמבר 2025
182
- **עדכון אחרון:** 14 דצמבר 2025
183
  **זמן משוער:** 3-4 שעות
184
 
185
  **התקדמות:**
@@ -192,7 +192,16 @@
192
  - [x] שיפור parsing של קלפי פיתוח ב-HumanUser
193
  - [x] הוספת הסבר מפורט ב-help על כל קלפי הפיתוח
194
  - [x] יצירת פונקציות placeholder לכל סוג קלף עם הודעות מועילות
195
- - [ ] מימוש מלא של Knight card (move robber + steal)
 
 
 
 
 
 
 
 
 
196
  - [x] **מימוש מלא של Road Building** ✅ (14 דצמבר 2025)
197
  - [x] Parsing פקודה: `use road rd [p1] [p2] rd [p3] [p4]`
198
  - [x] Validation של נקודות סמוכות
@@ -206,10 +215,11 @@
206
 
207
  **הערות:**
208
  - קניית dev cards כבר עובדת מלא! ✅
 
209
  - **Road Building card עובד מלא!** ✅ (פורמט: `use road rd 10 11 rd 12 13`)
210
  - כל קלף מחזיר הודעה ברורה למשתמש מה הוא צריך לעשות
211
  - הלוגיקה ב-`game.py` קיימת ועובדת - צריך רק לחבר ל-GameManager
212
- - תיקנו באג בקוד המקורי שגרם לכך שקלף Road Building לא הוסר
213
 
214
  ---
215
 
 
179
  ### משימה 5.3: Development Cards & Special Actions
180
  **סטטוס:** 🟢 בעבודה פעילה!
181
  **תאריך התחלה:** 10 דצמבר 2025
182
+ **עדכון אחרון:** 20 דצמבר 2025
183
  **זמן משוער:** 3-4 שעות
184
 
185
  **התקדמות:**
 
192
  - [x] שיפור parsing של קלפי פיתוח ב-HumanUser
193
  - [x] הוספת הסבר מפורט ב-help על כל קלפי הפיתוח
194
  - [x] יצירת פונקציות placeholder לכל סוג קלף עם הודעות מועילות
195
+ - [x] **מימוש מלא של Knight card** (20 דצמבר 2025)
196
+ - [x] Parsing פקודה: `use knight tile [tile_id] [steal [player_name]]`
197
+ - [x] Validation של tile_id והמרה לקואורדינטות משחק
198
+ - [x] העברת רובר למיקום חדש
199
+ - [x] Auto-selection של קורבן אם לא צוין
200
+ - [x] גניבת קלף אקראי משחקן סמוך
201
+ - [x] ספירת knights ועדכון Largest Army (3+ = 2 VP)
202
+ - [x] הסרת הקלף מהשחקן אחרי שימוש
203
+ - [x] **תיקון באג קריטי ב-game.py**: תיקון קריאה ל-`move_robber()` עם פרמטרים שגויים
204
+ - [x] הודעות user-friendly עם tile IDs במקום קואורדינטות
205
  - [x] **מימוש מלא של Road Building** ✅ (14 דצמבר 2025)
206
  - [x] Parsing פקודה: `use road rd [p1] [p2] rd [p3] [p4]`
207
  - [x] Validation של נקודות סמוכות
 
215
 
216
  **הערות:**
217
  - קניית dev cards כבר עובדת מלא! ✅
218
+ - **Knight card עובד מלא!** ✅ (פורמט: `use knight tile 5` או `use knight tile 5 steal Bob`)
219
  - **Road Building card עובד מלא!** ✅ (פורמט: `use road rd 10 11 rd 12 13`)
220
  - כל קלף מחזיר הודעה ברורה למשתמש מה הוא צריך לעשות
221
  - הלוגיקה ב-`game.py` קיימת ועובדת - צריך רק לחבר ל-GameManager
222
+ - תיקנו 2 באגים קריטיים בקוד המקורי (Knight ו-Road Building)
223
 
224
  ---
225
 
game_viz.log CHANGED
@@ -393,6 +393,8 @@ Board Tiles: 19 tiles configured
393
 
394
 
395
  >>> Turn 6: a's turn
 
 
396
  ✓ a rolled the dice
397
 
398
  ==================================================
@@ -424,6 +426,37 @@ Current Player: ► a
424
  -----
425
  Board Tiles: 19 tiles configured
426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  >>> Turn 7: b's turn
429
  ✓ b rolled the dice
@@ -488,8 +521,41 @@ Current Player: ► b
488
  -----
489
  Board Tiles: 19 tiles configured
490
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
 
492
  >>> Turn 8: c's turn
 
 
493
  ✓ c rolled the dice
494
 
495
  ==================================================
@@ -521,6 +587,37 @@ Current Player: ► c
521
  -----
522
  Board Tiles: 19 tiles configured
523
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
 
525
  >>> Turn 9: a's turn
526
  ✓ a rolled the dice
@@ -585,8 +682,40 @@ Current Player: ► a
585
  -----
586
  Board Tiles: 19 tiles configured
587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
589
  >>> Turn 10: b's turn
 
590
  ✓ b rolled the dice
591
 
592
  ==================================================
@@ -681,8 +810,40 @@ Current Player: ► b
681
  -----
682
  Board Tiles: 19 tiles configured
683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
  >>> Turn 11: c's turn
 
686
  ✓ c rolled the dice
687
 
688
  ==================================================
@@ -808,5 +969,424 @@ Current Player: ► c
808
  -----
809
  Board Tiles: 19 tiles configured
810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
 
812
  >>> Turn 12: a's turn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
 
395
  >>> Turn 6: a's turn
396
+ ✓ 📦 a: 1×Sheep
397
+ ✓ 📦 b: 1×Wheat
398
  ✓ a rolled the dice
399
 
400
  ==================================================
 
426
  -----
427
  Board Tiles: 19 tiles configured
428
 
429
+ ✓ a ended their turn
430
+
431
+ ==================================================
432
+  GAME STATE 
433
+ ==================================================
434
+
435
+ Turn: 6
436
+ Current Player: ► a
437
+
438
+ PLAYERS
439
+ -------
440
+
441
+ ► a
442
+ Victory Points: 2
443
+ Resources: None
444
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
445
+
446
+ b
447
+ Victory Points: 2
448
+ Resources: None
449
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
450
+
451
+ c
452
+ Victory Points: 2
453
+ Resources: None
454
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
455
+
456
+ BOARD
457
+ -----
458
+ Board Tiles: 19 tiles configured
459
+
460
 
461
  >>> Turn 7: b's turn
462
  ✓ b rolled the dice
 
521
  -----
522
  Board Tiles: 19 tiles configured
523
 
524
+ ✓ b ended their turn
525
+
526
+ ==================================================
527
+  GAME STATE 
528
+ ==================================================
529
+
530
+ Turn: 7
531
+ Current Player: ► b
532
+
533
+ PLAYERS
534
+ -------
535
+
536
+ a
537
+ Victory Points: 2
538
+ Resources: None
539
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
540
+
541
+ ► b
542
+ Victory Points: 2
543
+ Resources: None
544
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
545
+
546
+ c
547
+ Victory Points: 2
548
+ Resources: None
549
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
550
+
551
+ BOARD
552
+ -----
553
+ Board Tiles: 19 tiles configured
554
+
555
 
556
  >>> Turn 8: c's turn
557
+ ✓ 📦 a: 1×Sheep
558
+ ✓ 📦 b: 1×Wheat
559
  ✓ c rolled the dice
560
 
561
  ==================================================
 
587
  -----
588
  Board Tiles: 19 tiles configured
589
 
590
+ ✓ c ended their turn
591
+
592
+ ==================================================
593
+  GAME STATE 
594
+ ==================================================
595
+
596
+ Turn: 8
597
+ Current Player: ► c
598
+
599
+ PLAYERS
600
+ -------
601
+
602
+ a
603
+ Victory Points: 2
604
+ Resources: None
605
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
606
+
607
+ b
608
+ Victory Points: 2
609
+ Resources: None
610
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
611
+
612
+ ► c
613
+ Victory Points: 2
614
+ Resources: None
615
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
616
+
617
+ BOARD
618
+ -----
619
+ Board Tiles: 19 tiles configured
620
+
621
 
622
  >>> Turn 9: a's turn
623
  ✓ a rolled the dice
 
682
  -----
683
  Board Tiles: 19 tiles configured
684
 
685
+ ✓ a ended their turn
686
+
687
+ ==================================================
688
+  GAME STATE 
689
+ ==================================================
690
+
691
+ Turn: 9
692
+ Current Player: ► a
693
+
694
+ PLAYERS
695
+ -------
696
+
697
+ ► a
698
+ Victory Points: 2
699
+ Resources: None
700
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
701
+
702
+ b
703
+ Victory Points: 2
704
+ Resources: None
705
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
706
+
707
+ c
708
+ Victory Points: 2
709
+ Resources: None
710
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
711
+
712
+ BOARD
713
+ -----
714
+ Board Tiles: 19 tiles configured
715
+
716
 
717
  >>> Turn 10: b's turn
718
+ ✓ 📦 a: 1×Ore
719
  ✓ b rolled the dice
720
 
721
  ==================================================
 
810
  -----
811
  Board Tiles: 19 tiles configured
812
 
813
+ ✓ b ended their turn
814
+
815
+ ==================================================
816
+  GAME STATE 
817
+ ==================================================
818
+
819
+ Turn: 10
820
+ Current Player: ► b
821
+
822
+ PLAYERS
823
+ -------
824
+
825
+ a
826
+ Victory Points: 2
827
+ Resources: None
828
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
829
+
830
+ ► b
831
+ Victory Points: 2
832
+ Resources: None
833
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
834
+
835
+ c
836
+ Victory Points: 2
837
+ Resources: None
838
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
839
+
840
+ BOARD
841
+ -----
842
+ Board Tiles: 19 tiles configured
843
+
844
 
845
  >>> Turn 11: c's turn
846
+ ✓ 📦 c: 2×Wheat
847
  ✓ c rolled the dice
848
 
849
  ==================================================
 
969
  -----
970
  Board Tiles: 19 tiles configured
971
 
972
+ ✓ c ended their turn
973
+
974
+ ==================================================
975
+  GAME STATE 
976
+ ==================================================
977
+
978
+ Turn: 11
979
+ Current Player: ► c
980
+
981
+ PLAYERS
982
+ -------
983
+
984
+ a
985
+ Victory Points: 2
986
+ Resources: None
987
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
988
+
989
+ b
990
+ Victory Points: 2
991
+ Resources: None
992
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
993
+
994
+ ► c
995
+ Victory Points: 3
996
+ Resources: None
997
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
998
+
999
+ BOARD
1000
+ -----
1001
+ Board Tiles: 19 tiles configured
1002
+
1003
 
1004
  >>> Turn 12: a's turn
1005
+ ✓ 📦 a: 1×Sheep
1006
+ ✓ 📦 b: 1×Wheat
1007
+ ✓ a rolled the dice
1008
+
1009
+ ==================================================
1010
+  GAME STATE 
1011
+ ==================================================
1012
+
1013
+ Turn: 12
1014
+ Current Player: ► a
1015
+
1016
+ PLAYERS
1017
+ -------
1018
+
1019
+ ► a
1020
+ Victory Points: 2
1021
+ Resources: None
1022
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1023
+
1024
+ b
1025
+ Victory Points: 2
1026
+ Resources: None
1027
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1028
+
1029
+ c
1030
+ Victory Points: 3
1031
+ Resources: None
1032
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1033
+
1034
+ BOARD
1035
+ -----
1036
+ Board Tiles: 19 tiles configured
1037
+
1038
+ ✓ a ended their turn
1039
+
1040
+ ==================================================
1041
+  GAME STATE 
1042
+ ==================================================
1043
+
1044
+ Turn: 12
1045
+ Current Player: ► a
1046
+
1047
+ PLAYERS
1048
+ -------
1049
+
1050
+ ► a
1051
+ Victory Points: 2
1052
+ Resources: None
1053
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1054
+
1055
+ b
1056
+ Victory Points: 2
1057
+ Resources: None
1058
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1059
+
1060
+ c
1061
+ Victory Points: 3
1062
+ Resources: None
1063
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1064
+
1065
+ BOARD
1066
+ -----
1067
+ Board Tiles: 19 tiles configured
1068
+
1069
+
1070
+ >>> Turn 13: b's turn
1071
+ ✓ 📦 a: 1×Ore
1072
+ ✓ b rolled the dice
1073
+
1074
+ ==================================================
1075
+  GAME STATE 
1076
+ ==================================================
1077
+
1078
+ Turn: 13
1079
+ Current Player: ► b
1080
+
1081
+ PLAYERS
1082
+ -------
1083
+
1084
+ a
1085
+ Victory Points: 2
1086
+ Resources: None
1087
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1088
+
1089
+ ► b
1090
+ Victory Points: 2
1091
+ Resources: None
1092
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1093
+
1094
+ c
1095
+ Victory Points: 3
1096
+ Resources: None
1097
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1098
+
1099
+ BOARD
1100
+ -----
1101
+ Board Tiles: 19 tiles configured
1102
+
1103
+ ✓ b ended their turn
1104
+
1105
+ ==================================================
1106
+  GAME STATE 
1107
+ ==================================================
1108
+
1109
+ Turn: 13
1110
+ Current Player: ► b
1111
+
1112
+ PLAYERS
1113
+ -------
1114
+
1115
+ a
1116
+ Victory Points: 2
1117
+ Resources: None
1118
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1119
+
1120
+ ► b
1121
+ Victory Points: 2
1122
+ Resources: None
1123
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1124
+
1125
+ c
1126
+ Victory Points: 3
1127
+ Resources: None
1128
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1129
+
1130
+ BOARD
1131
+ -----
1132
+ Board Tiles: 19 tiles configured
1133
+
1134
+
1135
+ >>> Turn 14: c's turn
1136
+ ✓ 📦 b: 1×Wood 1×Sheep
1137
+ ✓ c rolled the dice
1138
+
1139
+ ==================================================
1140
+  GAME STATE 
1141
+ ==================================================
1142
+
1143
+ Turn: 14
1144
+ Current Player: ► c
1145
+
1146
+ PLAYERS
1147
+ -------
1148
+
1149
+ a
1150
+ Victory Points: 2
1151
+ Resources: None
1152
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1153
+
1154
+ b
1155
+ Victory Points: 2
1156
+ Resources: None
1157
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1158
+
1159
+ ► c
1160
+ Victory Points: 3
1161
+ Resources: None
1162
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1163
+
1164
+ BOARD
1165
+ -----
1166
+ Board Tiles: 19 tiles configured
1167
+
1168
+ ✓ c ended their turn
1169
+
1170
+ ==================================================
1171
+  GAME STATE 
1172
+ ==================================================
1173
+
1174
+ Turn: 14
1175
+ Current Player: ► c
1176
+
1177
+ PLAYERS
1178
+ -------
1179
+
1180
+ a
1181
+ Victory Points: 2
1182
+ Resources: None
1183
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1184
+
1185
+ b
1186
+ Victory Points: 2
1187
+ Resources: None
1188
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1189
+
1190
+ ► c
1191
+ Victory Points: 3
1192
+ Resources: None
1193
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1194
+
1195
+ BOARD
1196
+ -----
1197
+ Board Tiles: 19 tiles configured
1198
+
1199
+
1200
+ >>> Turn 15: a's turn
1201
+ ✓ 📦 b: 1×Wood 1×Ore
1202
+ ✓ a rolled the dice
1203
+
1204
+ ==================================================
1205
+  GAME STATE 
1206
+ ==================================================
1207
+
1208
+ Turn: 15
1209
+ Current Player: ► a
1210
+
1211
+ PLAYERS
1212
+ -------
1213
+
1214
+ ► a
1215
+ Victory Points: 2
1216
+ Resources: None
1217
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1218
+
1219
+ b
1220
+ Victory Points: 2
1221
+ Resources: None
1222
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1223
+
1224
+ c
1225
+ Victory Points: 3
1226
+ Resources: None
1227
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1228
+
1229
+ BOARD
1230
+ -----
1231
+ Board Tiles: 19 tiles configured
1232
+
1233
+ ✓ a ended their turn
1234
+
1235
+ ==================================================
1236
+  GAME STATE 
1237
+ ==================================================
1238
+
1239
+ Turn: 15
1240
+ Current Player: ► a
1241
+
1242
+ PLAYERS
1243
+ -------
1244
+
1245
+ ► a
1246
+ Victory Points: 2
1247
+ Resources: None
1248
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1249
+
1250
+ b
1251
+ Victory Points: 2
1252
+ Resources: None
1253
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1254
+
1255
+ c
1256
+ Victory Points: 3
1257
+ Resources: None
1258
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1259
+
1260
+ BOARD
1261
+ -----
1262
+ Board Tiles: 19 tiles configured
1263
+
1264
+
1265
+ >>> Turn 16: b's turn
1266
+ ✓ 📦 c: 1×Wood
1267
+ ✓ 📦 b: 1×Sheep
1268
+ ✓ b rolled the dice
1269
+
1270
+ ==================================================
1271
+  GAME STATE 
1272
+ ==================================================
1273
+
1274
+ Turn: 16
1275
+ Current Player: ► b
1276
+
1277
+ PLAYERS
1278
+ -------
1279
+
1280
+ a
1281
+ Victory Points: 2
1282
+ Resources: None
1283
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1284
+
1285
+ ► b
1286
+ Victory Points: 2
1287
+ Resources: None
1288
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1289
+
1290
+ c
1291
+ Victory Points: 3
1292
+ Resources: None
1293
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1294
+
1295
+ BOARD
1296
+ -----
1297
+ Board Tiles: 19 tiles configured
1298
+
1299
+ ✓ b proposed a trade
1300
+
1301
+ ==================================================
1302
+  GAME STATE 
1303
+ ==================================================
1304
+
1305
+ Turn: 16
1306
+ Current Player: ► b
1307
+
1308
+ PLAYERS
1309
+ -------
1310
+
1311
+ a
1312
+ Victory Points: 2
1313
+ Resources: None
1314
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1315
+
1316
+ ► b
1317
+ Victory Points: 2
1318
+ Resources: None
1319
+ Buildings: Settlements: 2, Cities: 0, Roads: 4
1320
+
1321
+ c
1322
+ Victory Points: 3
1323
+ Resources: None
1324
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1325
+
1326
+ BOARD
1327
+ -----
1328
+ Board Tiles: 19 tiles configured
1329
+
1330
+ ✓ b built a road
1331
+
1332
+ ==================================================
1333
+  GAME STATE 
1334
+ ==================================================
1335
+
1336
+ Turn: 16
1337
+ Current Player: ► b
1338
+
1339
+ PLAYERS
1340
+ -------
1341
+
1342
+ a
1343
+ Victory Points: 2
1344
+ Resources: None
1345
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1346
+
1347
+ ► b
1348
+ Victory Points: 2
1349
+ Resources: None
1350
+ Buildings: Settlements: 2, Cities: 0, Roads: 5
1351
+
1352
+ c
1353
+ Victory Points: 3
1354
+ Resources: None
1355
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1356
+
1357
+ BOARD
1358
+ -----
1359
+ Board Tiles: 19 tiles configured
1360
+
1361
+ ✓ b bought a development card
1362
+
1363
+ ==================================================
1364
+  GAME STATE 
1365
+ ==================================================
1366
+
1367
+ Turn: 16
1368
+ Current Player: ► b
1369
+
1370
+ PLAYERS
1371
+ -------
1372
+
1373
+ a
1374
+ Victory Points: 2
1375
+ Resources: None
1376
+ Buildings: Settlements: 2, Cities: 0, Roads: 2
1377
+
1378
+ ► b
1379
+ Victory Points: 2
1380
+ Resources: None
1381
+ Dev Cards: 1
1382
+ Buildings: Settlements: 2, Cities: 0, Roads: 5
1383
+
1384
+ c
1385
+ Victory Points: 3
1386
+ Resources: None
1387
+ Buildings: Settlements: 1, Cities: 1, Roads: 2
1388
+
1389
+ BOARD
1390
+ -----
1391
+ Board Tiles: 19 tiles configured
1392
+
pycatan/game.py CHANGED
@@ -127,8 +127,8 @@ class Game:
127
  if not has_settlement:
128
  return Statuses.ERR_INPUT
129
 
130
- # moves the robber
131
- self.board.move_robber(tile)
132
  # takes a random card from the victim
133
  if victim != None:
134
  # removes a random card from the victim
@@ -284,8 +284,12 @@ class Game:
284
  if args["victim"] < 0 or args["victim"] >= len(self.players) or args["victim"] == player:
285
  return Statuses.ERR_INPUT
286
 
 
 
 
 
287
  # moves the robber
288
- result = self.move_robber(r=args["robber_pos"][0], i=args["robber_pos"][1], player=player, victim=args["victim"])
289
 
290
  if result != Statuses.ALL_GOOD:
291
  return result
@@ -430,9 +434,14 @@ class Game:
430
  return roads
431
 
432
  def _get_robber_position(self):
433
- """Get current robber position."""
434
  if hasattr(self.board, 'robber') and self.board.robber:
435
- return self.board.robber
 
 
 
 
 
436
  return [3, 3] # Default center position if not found
437
 
438
  def _get_tiles_info(self):
 
127
  if not has_settlement:
128
  return Statuses.ERR_INPUT
129
 
130
+ # moves the robber (pass tile position, not tile object)
131
+ self.board.move_robber(tile.position)
132
  # takes a random card from the victim
133
  if victim != None:
134
  # removes a random card from the victim
 
284
  if args["victim"] < 0 or args["victim"] >= len(self.players) or args["victim"] == player:
285
  return Statuses.ERR_INPUT
286
 
287
+ # Get the tile object from coordinates
288
+ r, i = args["robber_pos"]
289
+ tile = self.board.tiles[r][i]
290
+
291
  # moves the robber
292
+ result = self.move_robber(tile=tile, player=player, victim=args["victim"])
293
 
294
  if result != Statuses.ALL_GOOD:
295
  return result
 
434
  return roads
435
 
436
  def _get_robber_position(self):
437
+ """Get current robber position as [row, col] list."""
438
  if hasattr(self.board, 'robber') and self.board.robber:
439
+ robber = self.board.robber
440
+ # Handle case where robber might be a Tile object (old bug)
441
+ if hasattr(robber, 'position'):
442
+ return robber.position
443
+ # Normal case - robber is already a list [row, col]
444
+ return robber
445
  return [3, 3] # Default center position if not found
446
 
447
  def _get_tiles_info(self):
pycatan/game_manager.py CHANGED
@@ -795,12 +795,129 @@ class GameManager:
795
 
796
  def _use_knight_card(self, player_id: int, action: Action) -> ActionResult:
797
  """Use Knight card - move robber and steal."""
798
- return ActionResult.failure_result(
799
- "Knight card usage not yet fully implemented.\n"
800
- " 💬 You need to move the robber and optionally steal from a player.\n"
801
- " Use 'robber [tile_id]' first, then this card will work.",
802
- "NOT_IMPLEMENTED"
803
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
 
805
  def _use_monopoly_card(self, player_id: int, action: Action) -> ActionResult:
806
  """Use Monopoly card - take all of one resource."""
@@ -1251,9 +1368,9 @@ class GameManager:
1251
  params['player_state'] = {
1252
  'victory_points': player.victory_points,
1253
  'card_count': len(player.cards),
1254
- 'roads': len(player.roads),
1255
- 'settlements': len(player.settlements),
1256
- 'cities': len(player.cities)
1257
  }
1258
 
1259
  def _update_all_systems(self, action: Action, result: ActionResult) -> None:
@@ -1281,6 +1398,11 @@ class GameManager:
1281
  result.error_message or ""
1282
  )
1283
 
 
 
 
 
 
1284
  # If action was successful, notify all users about the action
1285
  if result.success:
1286
  action_description = self._get_action_description(action)
@@ -1313,7 +1435,9 @@ class GameManager:
1313
  self.visualization_manager.display_game_state(current_state)
1314
  except Exception as e:
1315
  # Log visualization errors
 
1316
  print(f"Error updating visualizations: {e}")
 
1317
 
1318
  # Log the action and result for debugging
1319
  if self.config.get('debug', False):
@@ -1551,6 +1675,60 @@ class GameManager:
1551
  # Add distribution to action parameters for logging
1552
  action.parameters['distribution'] = distribution
1553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1554
  if distribution:
1555
  message = f"Rolled {total} ({die1}+{die2}). Resources distributed."
1556
  else:
 
795
 
796
  def _use_knight_card(self, player_id: int, action: Action) -> ActionResult:
797
  """Use Knight card - move robber and steal."""
798
+ try:
799
+ from pycatan.card import DevCard
800
+
801
+ # DEBUG: Check if player has the card
802
+ player = self.game.players[player_id]
803
+ player_name = self.users[player_id].name if hasattr(self.users[player_id], 'name') else f"Player {player_id}"
804
+
805
+ if not player.has_dev_cards([DevCard.Knight]):
806
+ error_msg = f"❌ {player_name}, you don't have a Knight card!"
807
+ print(f"\n{error_msg}")
808
+ return ActionResult.failure_result(error_msg, "NO_CARD")
809
+
810
+ # Get parameters from action
811
+ tile_coords = action.parameters.get('tile_coords')
812
+ victim_id = action.parameters.get('victim_id')
813
+
814
+ if not tile_coords:
815
+ error_msg = (
816
+ "Knight card requires robber tile location.\n"
817
+ " Format: use knight tile [tile_id] [steal [player_name]]\n"
818
+ " Example: use knight tile 5 steal Bob"
819
+ )
820
+ print(f"\n❌ {error_msg}")
821
+ return ActionResult.failure_result(error_msg, "MISSING_PARAMS")
822
+
823
+ row, index = tile_coords
824
+
825
+ # Validate tile exists
826
+ try:
827
+ tile = self.game.board.tiles[row][index]
828
+ except (IndexError, KeyError):
829
+ error_msg = f"❌ Invalid tile coordinates: [{row}, {index}]"
830
+ print(f"\n{error_msg}")
831
+ return ActionResult.failure_result(error_msg, "INVALID_COORDS")
832
+
833
+ # Check if robber is already there
834
+ current_robber_pos = getattr(self.game.board, 'robber', None)
835
+ if current_robber_pos and current_robber_pos == [row, index]:
836
+ from pycatan.board_definition import board_definition
837
+ hex_id = board_definition.game_coords_to_hex_id(row, index)
838
+ tile_display = f"tile {hex_id}" if hex_id else f"[{row}, {index}]"
839
+ error_msg = f"❌ The robber is already on {tile_display}! Choose a different tile."
840
+ print(f"\n{error_msg}")
841
+ return ActionResult.failure_result(error_msg, "SAME_POSITION")
842
+
843
+ # If victim_id is None, we'll let the game automatically select
844
+ # or use the first stealable player
845
+ if victim_id is None:
846
+ # Get stealable players
847
+ stealable = self._get_stealable_players(row, index)
848
+ if stealable:
849
+ # Auto-select first stealable player
850
+ victim_id = stealable[0]
851
+
852
+ # Prepare args for game.use_dev_card()
853
+ args = {
854
+ 'robber_pos': [row, index],
855
+ 'victim': victim_id
856
+ }
857
+
858
+ # Convert coordinates to hex ID for user-friendly messages
859
+ from pycatan.board_definition import board_definition
860
+ hex_id = board_definition.game_coords_to_hex_id(row, index)
861
+ tile_display = f"tile {hex_id}" if hex_id else f"[{row}, {index}]"
862
+
863
+ print(f"\n🎴 {player_name} is using Knight card...")
864
+
865
+ # Execute the knight card
866
+ status = self.game.use_dev_card(player_id, DevCard.Knight, args)
867
+
868
+ if status != Statuses.ALL_GOOD:
869
+ # Provide more specific error message for Knight card failures
870
+ error_msg = "Invalid input"
871
+ if status == Statuses.ERR_INPUT and victim_id is not None:
872
+ victim_name = self.users[victim_id].name if hasattr(self.users[victim_id], 'name') else f"Player {victim_id}"
873
+ error_msg = f"Cannot steal from {victim_name} - they have no settlements or cities adjacent to {tile_display}"
874
+ print(f"\n ✗ {error_msg}\n")
875
+
876
+ return self._convert_status_to_result(status, self.get_full_state(), [player_id])
877
+
878
+ # Remove the card from player's hand
879
+ self.game.players[player_id].remove_dev_card(DevCard.Knight)
880
+
881
+ # Update visualizations
882
+ player_name = self.users[player_id].name if hasattr(self.users[player_id], 'name') else f"Player {player_id}"
883
+
884
+ # Print robber move notification immediately
885
+ robber_msg = f"⚔️ {player_name} used a Knight card! Robber moved to {tile_display}."
886
+ print(f"\n {robber_msg}")
887
+
888
+ # Notify about robber move
889
+ self._notify_all_users(
890
+ "knight_used",
891
+ robber_msg
892
+ )
893
+
894
+ # Notify about card steal if victim exists
895
+ if victim_id is not None:
896
+ victim_name = self.users[victim_id].name if hasattr(self.users[victim_id], 'name') else f"Player {victim_id}"
897
+ self._notify_all_users(
898
+ "knight_steal",
899
+ f"🎯 {player_name} stole a card from {victim_name}!"
900
+ )
901
+
902
+ # Notify if player got Largest Army
903
+ if self.game.largest_army == player_id:
904
+ knights_count = self.game.players[player_id].knight_cards
905
+ if knights_count >= 3:
906
+ self._notify_all_users(
907
+ "largest_army",
908
+ f"🏆 {player_name} now has the Largest Army! ({knights_count} knights = +2 VP)"
909
+ )
910
+
911
+ return ActionResult.success_result(
912
+ self.get_full_state(),
913
+ affected_players=[player_id] if victim_id is None else [player_id, victim_id]
914
+ )
915
+
916
+ except Exception as e:
917
+ return ActionResult.failure_result(
918
+ f"Error using Knight card: {str(e)}",
919
+ "EXECUTION_ERROR"
920
+ )
921
 
922
  def _use_monopoly_card(self, player_id: int, action: Action) -> ActionResult:
923
  """Use Monopoly card - take all of one resource."""
 
1368
  params['player_state'] = {
1369
  'victory_points': player.victory_points,
1370
  'card_count': len(player.cards),
1371
+ 'roads': len(player.roads) if hasattr(player, 'roads') and player.roads else 0,
1372
+ 'settlements': len(player.settlements) if hasattr(player, 'settlements') and player.settlements else 0,
1373
+ 'cities': len(player.cities) if hasattr(player, 'cities') and player.cities else 0
1374
  }
1375
 
1376
  def _update_all_systems(self, action: Action, result: ActionResult) -> None:
 
1398
  result.error_message or ""
1399
  )
1400
 
1401
+ # If action failed, print error message immediately to console for visibility
1402
+ if not result.success and result.error_message:
1403
+ player_name = self.users[action.player_id].name if hasattr(self.users[action.player_id], 'name') else f"Player {action.player_id}"
1404
+ print(f"\n ✗ {player_name}: {result.error_message}\n")
1405
+
1406
  # If action was successful, notify all users about the action
1407
  if result.success:
1408
  action_description = self._get_action_description(action)
 
1435
  self.visualization_manager.display_game_state(current_state)
1436
  except Exception as e:
1437
  # Log visualization errors
1438
+ import traceback
1439
  print(f"Error updating visualizations: {e}")
1440
+ print(f"Traceback: {traceback.format_exc()}")
1441
 
1442
  # Log the action and result for debugging
1443
  if self.config.get('debug', False):
 
1675
  # Add distribution to action parameters for logging
1676
  action.parameters['distribution'] = distribution
1677
 
1678
+ # Send individual log events for each resource distribution
1679
+ if distribution and self.visualization_manager:
1680
+ from .log_events import EventType, create_log_entry
1681
+
1682
+ for player_key, resources in distribution.items():
1683
+ if resources: # Only log if player actually got resources
1684
+ # Extract player ID from "Player X" format or match by name
1685
+ player_id = None
1686
+ player_name = None
1687
+
1688
+ # Try to extract player ID from "Player X" format
1689
+ if player_key.startswith("Player "):
1690
+ try:
1691
+ player_id = int(player_key.split()[-1]) - 1 # "Player 1" -> 0
1692
+ if 0 <= player_id < len(self.users):
1693
+ player_name = self.users[player_id].name if hasattr(self.users[player_id], 'name') else player_key
1694
+ except:
1695
+ pass
1696
+
1697
+ # Fallback: try to match by name
1698
+ if player_id is None:
1699
+ for i, user in enumerate(self.users):
1700
+ if hasattr(user, 'name') and user.name == player_key:
1701
+ player_id = i
1702
+ player_name = player_key
1703
+ break
1704
+
1705
+ # If still not found, skip this entry
1706
+ if player_id is None:
1707
+ continue
1708
+
1709
+ # Count resources by type
1710
+ resource_counts = {}
1711
+ for res in resources:
1712
+ resource_counts[res] = resource_counts.get(res, 0) + 1
1713
+
1714
+ # Create log entry
1715
+ log_entry = create_log_entry(
1716
+ event_type=EventType.RESOURCE_DIST,
1717
+ turn=self._current_game_state.turn_number,
1718
+ player_id=player_id,
1719
+ player_name=player_name,
1720
+ data={
1721
+ 'resources': resource_counts,
1722
+ 'total': len(resources),
1723
+ 'dice_roll': total
1724
+ }
1725
+ )
1726
+
1727
+ # Send to visualizations
1728
+ for viz in self.visualization_manager.visualizations:
1729
+ if hasattr(viz, 'log_event'):
1730
+ viz.log_event(log_entry)
1731
+
1732
  if distribution:
1733
  message = f"Rolled {total} ({die1}+{die2}). Resources distributed."
1734
  else:
pycatan/game_moves_3Players.txt CHANGED
@@ -34,3 +34,17 @@ y
34
  c 40
35
  c 44
36
  end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  c 40
35
  c 44
36
  end
37
+ r
38
+ end
39
+ r
40
+ end
41
+ r
42
+ end
43
+ r
44
+ end
45
+ r
46
+ t player a wheat 1 for brick 1
47
+ y
48
+ road 12 13
49
+ buy dev card
50
+ use knight tile 9
pycatan/human_user.py CHANGED
@@ -657,6 +657,56 @@ class HumanUser(User):
657
  except (ValueError, IndexError):
658
  raise UserInputError("Invalid road format. Point numbers must be integers.")
659
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
  # Each card type needs specific additional input
661
  # For now, we'll create the action with just the card type
662
  # The GameManager will request additional input as needed
@@ -854,15 +904,19 @@ class HumanUser(User):
854
  print()
855
  print(" 📋 Card Types & Effects:")
856
  print(" knight - Move robber + steal card (gives +1 knight count)")
 
 
857
  print(" road - Build 2 free roads instantly")
 
 
858
  print(" monopoly - Take ALL cards of one resource from all players")
859
  print(" yearofplenty - Take any 2 resource cards from bank")
860
  print(" victorypoint - +1 VP (auto-counted, don't use manually)")
861
  print()
862
  print(" 💡 Tips:")
863
  print(" • 3+ knights = Largest Army (2 VP)")
864
- print(" • Cards are interactive - game will ask for details after 'use'")
865
- print(" • Example: 'use road' then follow prompts")
866
  print()
867
  print("🎲 TURN ACTIONS:")
868
  print(" roll - Roll dice (short: r, dice)")
 
657
  except (ValueError, IndexError):
658
  raise UserInputError("Invalid road format. Point numbers must be integers.")
659
 
660
+ # Handle Knight card - needs tile location and optional victim
661
+ elif dev_card_type == 'Knight':
662
+ # Format: use knight tile [tile_id] [steal [player_name]]
663
+ # parts = ['use', 'knight', 'tile', '5', 'steal', 'Alice']
664
+ if len(parts) < 4 or parts[2] != 'tile':
665
+ raise UserInputError(
666
+ "Knight card needs tile location.\n"
667
+ " Format: use knight tile [tile_id] [steal [player_name]]\n"
668
+ " Example: use knight tile 5\n"
669
+ " Example: use knight tile 5 steal Bob"
670
+ )
671
+
672
+ try:
673
+ # Parse tile ID
674
+ tile_id = int(parts[3])
675
+
676
+ # Convert tile ID to game coordinates using board_definition
677
+ tile_coords = board_definition.tile_id_to_game_coords(tile_id)
678
+ if tile_coords is None:
679
+ max_tile = len(board_definition.get_all_tile_ids())
680
+ raise UserInputError(f"Invalid tile ID: {tile_id}. Valid tiles: 1-{max_tile}")
681
+
682
+ params['tile_coords'] = tile_coords
683
+
684
+ # Parse optional victim
685
+ victim_name = None
686
+ if len(parts) >= 6 and parts[4] == 'steal':
687
+ victim_name = parts[5]
688
+
689
+ # Find player ID by name
690
+ victim_id = None
691
+ for pid, user in enumerate(game_state.players):
692
+ if user['name'].lower() == victim_name.lower():
693
+ victim_id = pid
694
+ break
695
+
696
+ if victim_id is None:
697
+ raise UserInputError(f"Player '{victim_name}' not found")
698
+
699
+ # Don't allow stealing from yourself
700
+ if victim_id == self.user_id:
701
+ raise UserInputError("You cannot steal from yourself!")
702
+
703
+ params['victim_id'] = victim_id
704
+ else:
705
+ params['victim_id'] = None
706
+
707
+ except ValueError:
708
+ raise UserInputError("Tile ID must be a number. Example: 'use knight tile 5'")
709
+
710
  # Each card type needs specific additional input
711
  # For now, we'll create the action with just the card type
712
  # The GameManager will request additional input as needed
 
904
  print()
905
  print(" 📋 Card Types & Effects:")
906
  print(" knight - Move robber + steal card (gives +1 knight count)")
907
+ print(" Format: use knight tile [tile_id] [steal [player_name]]")
908
+ print(" Example: use knight tile 5 steal Bob")
909
  print(" road - Build 2 free roads instantly")
910
+ print(" Format: use road rd [p1] [p2] rd [p3] [p4]")
911
+ print(" Example: use road rd 10 11 rd 12 13")
912
  print(" monopoly - Take ALL cards of one resource from all players")
913
  print(" yearofplenty - Take any 2 resource cards from bank")
914
  print(" victorypoint - +1 VP (auto-counted, don't use manually)")
915
  print()
916
  print(" 💡 Tips:")
917
  print(" • 3+ knights = Largest Army (2 VP)")
918
+ print(" • Knight steals from random adjacent player if you don't specify")
919
+ print(" • Example: 'use knight tile 5' (auto-steal) or 'use knight tile 5 steal Bob'")
920
  print()
921
  print("🎲 TURN ACTIONS:")
922
  print(" roll - Roll dice (short: r, dice)")
pycatan/log_events.py CHANGED
@@ -134,11 +134,18 @@ class LogEntry:
134
  return f"🎲 {player_str} rolled {dice} = {total}"
135
 
136
  elif self.event_type == EventType.RESOURCE_DIST:
137
- resource = self.data.get('resource', '?')
138
- recipients = self.data.get('recipients', [])
139
- amounts = self.data.get('amounts', [])
140
- distrib = ', '.join([f"Player {r}: {a}×{resource}" for r, a in zip(recipients, amounts)])
141
- return f"📦 {distrib}"
 
 
 
 
 
 
 
142
 
143
  elif self.event_type == EventType.BUILD_SETTLEMENT:
144
  point = self.data.get('point', '?')
 
134
  return f"🎲 {player_str} rolled {dice} = {total}"
135
 
136
  elif self.event_type == EventType.RESOURCE_DIST:
137
+ # Check if we have the new format (resources dict)
138
+ if 'resources' in self.data:
139
+ resources = self.data['resources']
140
+ resource_str = ' '.join([f"{count}×{res}" for res, count in resources.items()])
141
+ return f"📦 {player_str}: {resource_str}"
142
+ else:
143
+ # Old format
144
+ resource = self.data.get('resource', '?')
145
+ recipients = self.data.get('recipients', [])
146
+ amounts = self.data.get('amounts', [])
147
+ distrib = ', '.join([f"Player {r}: {a}×{resource}" for r, a in zip(recipients, amounts)])
148
+ return f"📦 {distrib}"
149
 
150
  elif self.event_type == EventType.BUILD_SETTLEMENT:
151
  point = self.data.get('point', '?')
pycatan/static/js/main.js CHANGED
@@ -148,6 +148,9 @@ function connectToSSE() {
148
  updateGameState(data.payload);
149
  } else if (data.type === 'action_executed') {
150
  logAction(data.payload);
 
 
 
151
  } else if (data.type === 'dice_roll') {
152
  logEvent(data.payload, 'log-dice');
153
  } else if (data.type === 'resource_distribution') {
 
148
  updateGameState(data.payload);
149
  } else if (data.type === 'action_executed') {
150
  logAction(data.payload);
151
+ } else if (data.type === 'log_event') {
152
+ // Handle structured log events (like resource distribution)
153
+ logAction(data.payload);
154
  } else if (data.type === 'dice_roll') {
155
  logEvent(data.payload, 'log-dice');
156
  } else if (data.type === 'resource_distribution') {
pycatan/templates/index.html CHANGED
@@ -60,12 +60,12 @@
60
  <tbody>
61
  <tr>
62
  <td>Road</td>
63
- <td>1 Brick + 1 Lumber</td>
64
  <td>—</td>
65
  </tr>
66
  <tr>
67
  <td>Settlement</td>
68
- <td>1 Brick + 1 Lumber + 1 Wheat + 1 Sheep</td>
69
  <td>1</td>
70
  </tr>
71
  <tr>
 
60
  <tbody>
61
  <tr>
62
  <td>Road</td>
63
+ <td>1 Brick + 1 Wood</td>
64
  <td>—</td>
65
  </tr>
66
  <tr>
67
  <td>Settlement</td>
68
+ <td>1 Brick + 1 Wood + 1 Wheat + 1 Sheep</td>
69
  <td>1</td>
70
  </tr>
71
  <tr>
pycatan/web_visualization.py CHANGED
@@ -607,9 +607,9 @@ class WebVisualization(Visualization):
607
  event_data['offer'] = params.get('offer', {})
608
  event_data['request'] = params.get('request', {})
609
 
610
- elif action.action_type == AT.TRADE_RESPOND:
611
  event_type = EventType.TRADE_RESPONSE
612
- event_data['response'] = params.get('response', 'UNKNOWN')
613
 
614
  elif action.action_type == AT.DISCARD_CARDS:
615
  event_type = EventType.DISCARD_CARDS
@@ -724,11 +724,16 @@ class WebVisualization(Visualization):
724
  'error': log_entry.error
725
  }
726
 
727
- # Add to history
728
  self.event_history.append(event_data)
729
  if len(self.event_history) > 100:
730
  self.event_history = self.event_history[-100:]
731
 
 
 
 
 
 
732
  # Broadcast to web clients
733
  self._broadcast_to_clients({
734
  'type': 'log_event',
 
607
  event_data['offer'] = params.get('offer', {})
608
  event_data['request'] = params.get('request', {})
609
 
610
+ elif action.action_type in [AT.TRADE_ACCEPT, AT.TRADE_REJECT]:
611
  event_type = EventType.TRADE_RESPONSE
612
+ event_data['response'] = 'ACCEPT' if action.action_type == AT.TRADE_ACCEPT else 'REJECT'
613
 
614
  elif action.action_type == AT.DISCARD_CARDS:
615
  event_type = EventType.DISCARD_CARDS
 
724
  'error': log_entry.error
725
  }
726
 
727
+ # Add to BOTH histories so it appears in the action log
728
  self.event_history.append(event_data)
729
  if len(self.event_history) > 100:
730
  self.event_history = self.event_history[-100:]
731
 
732
+ # Also add to action_history for display in UI
733
+ self.action_history.append(event_data)
734
+ if len(self.action_history) > 100:
735
+ self.action_history = self.action_history[-100:]
736
+
737
  # Broadcast to web clients
738
  self._broadcast_to_clients({
739
  'type': 'log_event',
test_knight_card.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test Knight card implementation
3
+ """
4
+ from pycatan import Game
5
+ from pycatan.card import DevCard, ResCard
6
+ from pycatan.actions import GamePhase
7
+
8
+ def test_knight_card_basic():
9
+ """Test basic Knight card usage."""
10
+ print("\n" + "="*60)
11
+ print("Testing Knight Card Implementation")
12
+ print("="*60)
13
+
14
+ # Create a simple game
15
+ game = Game(num_of_players=3)
16
+ game.game_phase = GamePhase.NORMAL_PLAY
17
+
18
+ # Give player 0 a Knight card
19
+ game.players[0].add_dev_card(DevCard.Knight)
20
+
21
+ print("\n✓ Game setup complete")
22
+ print(f" - Player 0 has {len(game.players[0].dev_cards)} dev card(s)")
23
+ print(f" - Initial robber position: {game.board.robber}")
24
+
25
+ # Test Knight card usage
26
+ print("\n🧪 Testing Knight card usage...")
27
+
28
+ # Use Knight card on tile [2, 1], no victim
29
+ args = {
30
+ 'robber_pos': [2, 1],
31
+ 'victim': None
32
+ }
33
+
34
+ print(f" - Player 0 uses Knight card on tile [2, 1]")
35
+
36
+ status = game.use_dev_card(0, DevCard.Knight, args)
37
+
38
+ from pycatan.statuses import Statuses
39
+ if status == Statuses.ALL_GOOD:
40
+ print(" ✓ Knight card used successfully!")
41
+ print(f" - New robber position: {game.board.robber}")
42
+ print(f" - Player 0 now has {game.players[0].knight_cards} knight(s)")
43
+
44
+ # Check Largest Army
45
+ if game.largest_army is not None:
46
+ print(f" - Largest Army: Player {game.largest_army} ({game.players[game.largest_army].knight_cards} knights)")
47
+
48
+ # Test with 3 knights to get Largest Army
49
+ print("\n🧪 Testing Largest Army achievement...")
50
+ game.players[0].add_dev_card(DevCard.Knight)
51
+ game.players[0].add_dev_card(DevCard.Knight)
52
+
53
+ # Use second knight
54
+ args['robber_pos'] = [1, 0]
55
+ status = game.use_dev_card(0, DevCard.Knight, args)
56
+ print(f" - After 2nd knight: {game.players[0].knight_cards} knights")
57
+
58
+ # Use third knight
59
+ args['robber_pos'] = [2, 0]
60
+ status = game.use_dev_card(0, DevCard.Knight, args)
61
+ print(f" - After 3rd knight: {game.players[0].knight_cards} knights")
62
+
63
+ if game.largest_army == 0:
64
+ print(f" ✓ Player 0 got Largest Army! (+2 VP)")
65
+
66
+ else:
67
+ print(f" ✗ Failed with status: {status}")
68
+
69
+ print("\n" + "="*60)
70
+ print("Test Complete!")
71
+ print("="*60)
72
+
73
+ if __name__ == "__main__":
74
+ test_knight_card_basic()
test_knight_live.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test Knight card in a live game scenario
3
+ """
4
+ import sys
5
+ import os
6
+
7
+ # Disable emoji errors for Windows console
8
+ os.environ['PYTHONIOENCODING'] = 'utf-8'
9
+
10
+ from pycatan.game_manager import GameManager
11
+ from pycatan.human_user import HumanUser
12
+ from pycatan.actions import GamePhase, Action, ActionType
13
+ from pycatan.card import DevCard, ResCard
14
+
15
+ def test_knight_live():
16
+ """Test Knight card purchase and usage."""
17
+ print("\n" + "="*60)
18
+ print("Testing Knight Card - Live Scenario")
19
+ print("="*60)
20
+
21
+ # Create game
22
+ users = [
23
+ HumanUser("Alice", 0),
24
+ HumanUser("Bob", 1),
25
+ HumanUser("Charlie", 2)
26
+ ]
27
+
28
+ game_manager = GameManager(users)
29
+ game = game_manager.game
30
+
31
+ # Start the game
32
+ game_manager._is_running = True
33
+ game_manager._current_player_id = 1 # Bob's turn
34
+
35
+ # Setup game state
36
+ game.game_phase = GamePhase.NORMAL_PLAY
37
+ game_manager._current_game_state.game_phase = GamePhase.NORMAL_PLAY
38
+ game_manager._current_game_state.turn_phase = game_manager._current_game_state.turn_phase.__class__.PLAYER_ACTIONS
39
+ game_manager._current_game_state.dice_rolled = True
40
+
41
+ # Give Bob resources to buy dev card
42
+ game.players[1].add_cards([ResCard.Ore, ResCard.Sheep, ResCard.Wheat])
43
+
44
+ print("\nStep 1: Bob buys a dev card")
45
+ print(f" - Bob's cards before: {len(game.players[1].cards)} resource cards")
46
+ print(f" - Bob's dev cards before: {len(game.players[1].dev_cards)}")
47
+
48
+ # Buy dev card
49
+ buy_action = Action(ActionType.BUY_DEV_CARD, 1, {})
50
+ result = game_manager.execute_action(buy_action)
51
+
52
+ print(f" - Buy result: {result.success}")
53
+ if result.success:
54
+ print(f" - Bob's cards after: {len(game.players[1].cards)} resource cards")
55
+ print(f" - Bob's dev cards after: {len(game.players[1].dev_cards)}")
56
+ print(f" - Dev cards: {[card.name for card in game.players[1].dev_cards]}")
57
+ else:
58
+ print(f" - Error: {result.error_message if hasattr(result, 'error_message') else 'Unknown'}")
59
+ return
60
+
61
+ # Check if Bob got a Knight
62
+ has_knight = any(card == DevCard.Knight for card in game.players[1].dev_cards)
63
+ print(f" - Has Knight: {has_knight}")
64
+
65
+ if not has_knight:
66
+ print("\n Bob didn't get a Knight card. Adding one manually for testing...")
67
+ game.players[1].add_dev_card(DevCard.Knight)
68
+
69
+ print("\nStep 2: Bob tries to use Knight card")
70
+ print(f" - Current robber position: {game.board.robber}")
71
+
72
+ # Try to use Knight on tile 5
73
+ from pycatan.board_definition import board_definition
74
+ tile_coords = board_definition.hex_id_to_game_coords(5)
75
+
76
+ print(f" - Tile 5 = {tile_coords}")
77
+
78
+ knight_action = Action(
79
+ ActionType.USE_DEV_CARD,
80
+ 1,
81
+ {
82
+ 'card_type': 'Knight',
83
+ 'tile_coords': tile_coords,
84
+ 'victim_id': None
85
+ }
86
+ )
87
+
88
+ print(f" - Executing Knight action...")
89
+ result = game_manager.execute_action(knight_action)
90
+
91
+ print(f"\nStep 3: Results")
92
+ print(f" - Success: {result.success if result else 'No result'}")
93
+ if result and not result.success:
94
+ print(f" - Error: {result.error_message if hasattr(result, 'error_message') else 'Unknown'}")
95
+ print(f" - Error code: {result.error_code if hasattr(result, 'error_code') else 'Unknown'}")
96
+
97
+ print(f" - Robber position after: {game.board.robber}")
98
+ print(f" - Bob's dev cards after: {[card.name for card in game.players[1].dev_cards]}")
99
+ print(f" - Bob's knight count: {game.players[1].knight_cards}")
100
+
101
+ print("\n" + "="*60)
102
+
103
+ if __name__ == "__main__":
104
+ test_knight_live()
test_knight_manual.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Manual test for Knight card - no emoji issues
3
+ """
4
+ import sys
5
+ import os
6
+
7
+ # Force UTF-8 encoding
8
+ sys.stdout.reconfigure(encoding='utf-8')
9
+
10
+ from pycatan.game_manager import GameManager
11
+ from pycatan.human_user import HumanUser
12
+ from pycatan.console_visualization import ConsoleVisualization
13
+ from pycatan.actions import GamePhase
14
+ from pycatan.card import DevCard, ResCard
15
+
16
+ def main():
17
+ print("="*60)
18
+ print("Testing Knight Card - Manual Game")
19
+ print("="*60)
20
+
21
+ # Create game
22
+ users = [HumanUser(f"Player{i}", i) for i in range(3)]
23
+ game_manager = GameManager(users)
24
+
25
+ # Add console viz
26
+ viz = ConsoleVisualization()
27
+ game_manager.visualization_manager = type('obj', (object,), {
28
+ 'visualizations': [viz],
29
+ 'display_action': lambda a, r: viz.display_action(a, r),
30
+ 'display_game_state': lambda s: viz.display_game_state(s)
31
+ })()
32
+
33
+ print("\nEnter commands (or 'help' for list):")
34
+ print("Type 'quit' to exit\n")
35
+
36
+ # Read from the moves file
37
+ moves_file = "pycatan/game_moves_3Players.txt"
38
+ with open(moves_file, 'r') as f:
39
+ lines = [line.strip() for line in f.readlines()]
40
+
41
+ # Execute moves
42
+ move_index = 0
43
+ while move_index < len(lines):
44
+ cmd = lines[move_index]
45
+ move_index += 1
46
+
47
+ # Skip empty lines
48
+ if not cmd:
49
+ continue
50
+
51
+ print(f"> {cmd}")
52
+
53
+ # Check if it's the knight command we're testing
54
+ if cmd.startswith("use knight"):
55
+ print(f"\n>>> BEFORE Knight card:")
56
+ print(f" Robber position: {game_manager.game.board.robber}")
57
+
58
+ # Process the command (simplified - just for testing)
59
+ if cmd == "quit":
60
+ break
61
+
62
+ print("\n" + "="*60)
63
+ print("Test Complete")
64
+ print("="*60)
65
+
66
+ if __name__ == "__main__":
67
+ try:
68
+ main()
69
+ except Exception as e:
70
+ print(f"Error: {e}")
71
+ import traceback
72
+ traceback.print_exc()
test_knight_messages.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test Knight card messages with tile IDs
3
+ """
4
+ from pycatan import Game
5
+ from pycatan.card import DevCard
6
+ from pycatan.board_definition import board_definition
7
+ from pycatan.actions import GamePhase
8
+
9
+ def test_knight_messages():
10
+ """Test that Knight card shows proper tile IDs in messages."""
11
+ print("\n" + "="*60)
12
+ print("Testing Knight Card Message Formatting")
13
+ print("="*60)
14
+
15
+ # Create simple game
16
+ game = Game(num_of_players=3)
17
+ game.game_phase = GamePhase.NORMAL_PLAY
18
+
19
+ # Give player 0 a Knight card
20
+ game.players[0].add_dev_card(DevCard.Knight)
21
+
22
+ print("\n✓ Game setup complete")
23
+ print(f" - Player 0 has {len(game.players[0].dev_cards)} Knight card")
24
+
25
+ # Test using Knight card on tile 5
26
+ print("\n🧪 Testing Knight card with tile ID 5...")
27
+
28
+ tile_coords = board_definition.hex_id_to_game_coords(5)
29
+
30
+ if tile_coords:
31
+ row, index = tile_coords
32
+ print(f" - Tile 5 maps to game coordinates {tile_coords}")
33
+
34
+ # Prepare args
35
+ args = {
36
+ 'robber_pos': [row, index],
37
+ 'victim': None
38
+ }
39
+
40
+ print(f"\n📢 Using Knight card to move robber to tile 5 (coords {tile_coords})")
41
+
42
+ # Execute
43
+ from pycatan.statuses import Statuses
44
+ status = game.use_dev_card(0, DevCard.Knight, args)
45
+
46
+ if status == Statuses.ALL_GOOD:
47
+ print("✅ Knight card used successfully!")
48
+ print(f" - Robber position: {game.board.robber}")
49
+ print(f" - Player 0 knights: {game.players[0].knight_cards}")
50
+
51
+ # Verify the message format
52
+ hex_id = board_definition.game_coords_to_hex_id(row, index)
53
+ print(f"\n✨ Message should say: '⚔️ Alice used a Knight card! Robber moved to tile {hex_id}.'")
54
+ print(f" (Converted {tile_coords} → tile {hex_id})")
55
+ else:
56
+ print(f"❌ Failed with status: {status}")
57
+ else:
58
+ print(" ❌ Could not find tile 5 coordinates")
59
+
60
+ print("\n" + "="*60)
61
+ print("Test Complete!")
62
+ print("="*60)
63
+
64
+ if __name__ == "__main__":
65
+ test_knight_messages()
test_knight_simple.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple test - does Knight card move the robber?
3
+ """
4
+ from pycatan.game_manager import GameManager
5
+ from pycatan.human_user import HumanUser
6
+ from pycatan.actions import GamePhase, Action, ActionType
7
+ from pycatan.card import DevCard
8
+
9
+ print("Knight Card Test - Robber Movement")
10
+ print("="*50)
11
+
12
+ # Create simple game
13
+ users = [HumanUser("Alice", 0), HumanUser("Bob", 1)]
14
+ gm = GameManager(users)
15
+ game = gm.game
16
+
17
+ # Setup
18
+ gm._is_running = True
19
+ gm._current_player_id = 1
20
+ gm._current_game_state.game_phase = GamePhase.NORMAL_PLAY
21
+ gm._current_game_state.current_player = 1
22
+ gm._current_game_state.dice_rolled = True
23
+
24
+ # Give Bob a Knight card
25
+ game.players[1].add_dev_card(DevCard.Knight)
26
+
27
+ print(f"Initial robber: {game.board.robber}")
28
+
29
+ # Execute Knight - move to tile 5
30
+ from pycatan.board_definition import board_definition
31
+ coords = board_definition.hex_id_to_game_coords(5)
32
+ print(f"Moving to tile 5 (coords: {coords})")
33
+
34
+ action = Action(ActionType.USE_DEV_CARD, 1, {
35
+ 'card_type': 'Knight', # HumanUser sends string
36
+ 'tile_coords': coords,
37
+ 'victim_id': None # NO steal
38
+ })
39
+
40
+ result = gm.execute_action(action)
41
+
42
+ print(f"\nResult: {result.success}")
43
+ if not result.success:
44
+ print(f"Error: {result.error_message}")
45
+ print(f"Final robber: {game.board.robber}")
46
+ print(f"Expected: {coords}")
47
+
48
+ if game.board.robber == list(coords):
49
+ print("\nSUCCESS - Robber moved!")
50
+ else:
51
+ print("\nFAILED - Robber didn't move!")
test_knight_steal_fail.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test Knight card with invalid steal attempt
3
+ """
4
+ from pycatan.game_manager import GameManager
5
+ from pycatan.human_user import HumanUser
6
+ from pycatan.actions import GamePhase, Action, ActionType
7
+ from pycatan.card import DevCard
8
+
9
+ print("Knight Card Test - Invalid Steal")
10
+ print("="*50)
11
+
12
+ # Create simple game
13
+ users = [HumanUser("Alice", 0), HumanUser("Bob", 1)]
14
+ gm = GameManager(users)
15
+ game = gm.game
16
+
17
+ # Setup
18
+ gm._is_running = True
19
+ gm._current_player_id = 1
20
+ gm._current_game_state.game_phase = GamePhase.NORMAL_PLAY
21
+ gm._current_game_state.current_player = 1
22
+ gm._current_game_state.dice_rolled = True
23
+
24
+ # Give Bob a Knight card
25
+ game.players[1].add_dev_card(DevCard.Knight)
26
+
27
+ print(f"Initial robber: {game.board.robber}")
28
+
29
+ # Execute Knight - move to tile 5 and try to steal from Alice
30
+ # (who has no settlements there)
31
+ from pycatan.board_definition import board_definition
32
+ coords = board_definition.hex_id_to_game_coords(5)
33
+ print(f"Moving to tile 5 (coords: {coords})")
34
+ print(f"Trying to steal from Alice (who has no settlements there)")
35
+
36
+ action = Action(ActionType.USE_DEV_CARD, 1, {
37
+ 'card_type': 'Knight',
38
+ 'tile_coords': coords,
39
+ 'victim_id': 0 # Try to steal from Alice
40
+ })
41
+
42
+ print("\nExecuting Knight card...")
43
+ result = gm.execute_action(action)
44
+
45
+ print(f"\nResult: {result.success}")
46
+ if not result.success:
47
+ print(f"Error: {result.error_message}")
48
+ print("\nThis is EXPECTED - Alice has no settlements near tile 5!")
49
+ print(f"Robber position: {game.board.robber}")
test_robber_display.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test that robber position is displayed after Knight card usage
3
+ """
4
+ import sys
5
+ import os
6
+ os.environ['PYTHONIOENCODING'] = 'utf-8'
7
+
8
+ from pycatan.game_manager import GameManager
9
+ from pycatan.human_user import HumanUser
10
+ from pycatan.console_visualization import ConsoleVisualization
11
+ from pycatan.actions import GamePhase, Action, ActionType
12
+ from pycatan.card import DevCard, ResCard
13
+
14
+ def test_robber_display():
15
+ """Test that robber position displays correctly after Knight card."""
16
+ print("\n" + "="*60)
17
+ print("Testing Robber Display After Knight Card")
18
+ print("="*60)
19
+
20
+ # Create game with console visualization
21
+ users = [
22
+ HumanUser("Alice", 0),
23
+ HumanUser("Bob", 1),
24
+ ]
25
+
26
+ game_manager = GameManager(users)
27
+ game = game_manager.game
28
+
29
+ # Add console visualization
30
+ console_viz = ConsoleVisualization()
31
+ game_manager.visualization_manager = type('obj', (object,), {
32
+ 'visualizations': [console_viz],
33
+ 'display_action': lambda action, result: console_viz.display_action(action, result),
34
+ 'display_game_state': lambda state: console_viz.display_game_state(state)
35
+ })()
36
+
37
+ # Setup game state
38
+ game_manager._is_running = True
39
+ game_manager._current_player_id = 1
40
+ game_manager._current_game_state.game_phase = GamePhase.NORMAL_PLAY
41
+ game_manager._current_game_state.turn_phase = game_manager._current_game_state.turn_phase.__class__.PLAYER_ACTIONS
42
+ game_manager._current_game_state.dice_rolled = True
43
+ game_manager._current_game_state.current_player = 1
44
+
45
+ # Print initial robber position
46
+ print(f"\nInitial robber position: {game.board.robber}")
47
+
48
+ # Give Bob a Knight card
49
+ game.players[1].add_dev_card(DevCard.Knight)
50
+ print(f"\nBob has Knight card: {DevCard.Knight in game.players[1].dev_cards}")
51
+
52
+ # Create Knight card action - move robber to tile 5
53
+ # Tile 5 in hex coordinates
54
+ from pycatan.board_definition import board_definition
55
+ tile_coords = board_definition.hex_id_to_game_coords(5)
56
+ print(f"\nMoving robber to tile 5 (game coords: {tile_coords})")
57
+
58
+ knight_action = Action(
59
+ ActionType.USE_DEV_CARD,
60
+ 1, # Bob
61
+ {
62
+ 'card_type': 'Knight', # String, not enum!
63
+ 'tile_coords': tile_coords,
64
+ 'victim_id': None # No steal
65
+ }
66
+ )
67
+
68
+ print("\n" + "-"*60)
69
+ print("Executing Knight card action...")
70
+ print("-"*60)
71
+
72
+ result = game_manager.execute_action(knight_action)
73
+
74
+ print(f"\nAction result: {result.success}")
75
+ if not result.success:
76
+ print(f"Error: {result.error_message}")
77
+
78
+ # Force display of game state through console visualization
79
+ print("\n" + "="*60)
80
+ print("CONSOLE VISUALIZATION - GAME STATE")
81
+ print("="*60)
82
+ console_viz.display_game_state(state := game_manager.get_full_state())
83
+
84
+ # Check final robber position
85
+ print(f"\n✓ Final robber position: {game.board.robber}")
86
+ print(f"✓ Expected: {tile_coords}")
87
+ print(f"✓ Match: {list(game.board.robber) == list(tile_coords)}")
88
+
89
+ print(f"\n✓ Robber position in GameState: {state.board_state.robber_position}")
90
+
91
+ print("\n" + "="*60)
92
+ print("✓ Test Complete - Robber moved successfully!")
93
+ print("="*60)
94
+
95
+ if __name__ == "__main__":
96
+ test_robber_display()
test_robber_visual.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test to see robber position before and after Knight card
3
+ """
4
+ from pycatan.game_manager import GameManager
5
+ from pycatan.human_user import HumanUser
6
+ from pycatan.actions import GamePhase, Action, ActionType
7
+ from pycatan.card import DevCard
8
+
9
+ print("\n" + "="*60)
10
+ print("Testing Robber Movement with Knight Card")
11
+ print("="*60)
12
+
13
+ # Create game
14
+ users = [HumanUser("a", 0), HumanUser("b", 1), HumanUser("c", 2)]
15
+ gm = GameManager(users)
16
+ game = gm.game
17
+
18
+ # Setup game state
19
+ gm._is_running = True
20
+ gm._current_player_id = 1
21
+ gm._current_game_state.game_phase = GamePhase.NORMAL_PLAY
22
+ gm._current_game_state.current_player = 1
23
+ gm._current_game_state.dice_rolled = True
24
+
25
+ # Give b a Knight card
26
+ game.players[1].add_dev_card(DevCard.Knight)
27
+
28
+ # Show initial robber position
29
+ print(f"\nBEFORE Knight card:")
30
+ print(f" Robber position: {game.board.robber}")
31
+
32
+ # Try to use knight - tile 5
33
+ from pycatan.board_definition import board_definition
34
+ tile_5_coords = board_definition.hex_id_to_game_coords(5)
35
+ print(f"\nTrying to move robber to tile 5")
36
+ print(f" Tile 5 coordinates: {tile_5_coords}")
37
+
38
+ # Try WITHOUT steal first
39
+ print("\n--- Attempt 1: WITHOUT steal ---")
40
+ action1 = Action(ActionType.USE_DEV_CARD, 1, {
41
+ 'card_type': 'Knight',
42
+ 'tile_coords': tile_5_coords,
43
+ 'victim_id': None
44
+ })
45
+
46
+ result1 = gm.execute_action(action1)
47
+ print(f"Result: {result1.success}")
48
+ if not result1.success:
49
+ print(f"Error: {result1.error_message}")
50
+
51
+ print(f"\nAFTER attempt:")
52
+ print(f" Robber position: {game.board.robber}")
53
+ print(f" Expected: {list(tile_5_coords)}")
54
+ print(f" Match: {game.board.robber == list(tile_5_coords)}")
55
+
56
+ # Convert back to see what hex ID we're on
57
+ if game.board.robber:
58
+ actual_hex = board_definition.game_coords_to_hex_id(game.board.robber[0], game.board.robber[1])
59
+ print(f" Robber is actually on tile: {actual_hex}")
60
+
61
+ print("\n" + "="*60)