khetai 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +36 -22
  5. data/ext/khetai/dev/README.md +15 -0
  6. data/ext/khetai/dev/fltk-ui/Makefile +65 -0
  7. data/ext/khetai/dev/fltk-ui/Makefile.khetai +46 -0
  8. data/ext/khetai/dev/fltk-ui/README.md +34 -0
  9. data/ext/khetai/dev/fltk-ui/ai_loader.cpp +93 -0
  10. data/ext/khetai/dev/fltk-ui/ai_loader.h +33 -0
  11. data/ext/khetai/dev/fltk-ui/assets/anubis_red_e.png +0 -0
  12. data/ext/khetai/dev/fltk-ui/assets/anubis_red_n.png +0 -0
  13. data/ext/khetai/dev/fltk-ui/assets/anubis_red_s.png +0 -0
  14. data/ext/khetai/dev/fltk-ui/assets/anubis_red_w.png +0 -0
  15. data/ext/khetai/dev/fltk-ui/assets/anubis_silver_e.png +0 -0
  16. data/ext/khetai/dev/fltk-ui/assets/anubis_silver_n.png +0 -0
  17. data/ext/khetai/dev/fltk-ui/assets/anubis_silver_s.png +0 -0
  18. data/ext/khetai/dev/fltk-ui/assets/anubis_silver_w.png +0 -0
  19. data/ext/khetai/dev/fltk-ui/assets/example_board.png +0 -0
  20. data/ext/khetai/dev/fltk-ui/assets/laser_red_e.png +0 -0
  21. data/ext/khetai/dev/fltk-ui/assets/laser_red_n.png +0 -0
  22. data/ext/khetai/dev/fltk-ui/assets/laser_red_s.png +0 -0
  23. data/ext/khetai/dev/fltk-ui/assets/laser_red_w.png +0 -0
  24. data/ext/khetai/dev/fltk-ui/assets/laser_silver_e.png +0 -0
  25. data/ext/khetai/dev/fltk-ui/assets/laser_silver_n.png +0 -0
  26. data/ext/khetai/dev/fltk-ui/assets/laser_silver_s.png +0 -0
  27. data/ext/khetai/dev/fltk-ui/assets/laser_silver_w.png +0 -0
  28. data/ext/khetai/dev/fltk-ui/assets/pharaoh_red.png +0 -0
  29. data/ext/khetai/dev/fltk-ui/assets/pharaoh_silver.png +0 -0
  30. data/ext/khetai/dev/fltk-ui/assets/pyramid_red_e.png +0 -0
  31. data/ext/khetai/dev/fltk-ui/assets/pyramid_red_n.png +0 -0
  32. data/ext/khetai/dev/fltk-ui/assets/pyramid_red_s.png +0 -0
  33. data/ext/khetai/dev/fltk-ui/assets/pyramid_red_w.png +0 -0
  34. data/ext/khetai/dev/fltk-ui/assets/pyramid_silver_e.png +0 -0
  35. data/ext/khetai/dev/fltk-ui/assets/pyramid_silver_n.png +0 -0
  36. data/ext/khetai/dev/fltk-ui/assets/pyramid_silver_s.png +0 -0
  37. data/ext/khetai/dev/fltk-ui/assets/pyramid_silver_w.png +0 -0
  38. data/ext/khetai/dev/fltk-ui/assets/scarab_red_e.png +0 -0
  39. data/ext/khetai/dev/fltk-ui/assets/scarab_red_n.png +0 -0
  40. data/ext/khetai/dev/fltk-ui/assets/scarab_red_s.png +0 -0
  41. data/ext/khetai/dev/fltk-ui/assets/scarab_red_w.png +0 -0
  42. data/ext/khetai/dev/fltk-ui/assets/scarab_silver_e.png +0 -0
  43. data/ext/khetai/dev/fltk-ui/assets/scarab_silver_n.png +0 -0
  44. data/ext/khetai/dev/fltk-ui/assets/scarab_silver_s.png +0 -0
  45. data/ext/khetai/dev/fltk-ui/assets/scarab_silver_w.png +0 -0
  46. data/ext/khetai/dev/fltk-ui/build_khetai.sh +9 -0
  47. data/ext/khetai/dev/fltk-ui/game_board.cpp +896 -0
  48. data/ext/khetai/dev/fltk-ui/game_board.h +105 -0
  49. data/ext/khetai/dev/fltk-ui/game_board_util.cpp +119 -0
  50. data/ext/khetai/dev/fltk-ui/game_board_util.h +15 -0
  51. data/ext/khetai/dev/fltk-ui/khet.cpp +59 -0
  52. data/ext/khetai/dev/main.c +8 -7
  53. data/ext/khetai/dev/main.rb +9 -10
  54. data/ext/khetai/khetai.c +22 -6
  55. data/ext/khetai/khetai_lib.c +24 -13
  56. data/ext/khetai/khetai_lib.h +51 -24
  57. data/lib/khetai/version.rb +1 -1
  58. metadata +50 -4
  59. data/README_ORIGINAL.md +0 -40
@@ -0,0 +1,896 @@
1
+ #include "game_board.h"
2
+ #include "game_board_util.h"
3
+ #include "../../khetai_lib.h"
4
+
5
+ #include <FL/fl_draw.H>
6
+ #include <FL/Fl.H>
7
+ #include <iostream>
8
+ #include <cstring>
9
+
10
+ GameBoard::GameBoard(int X, int Y, int W, int H, const char *L)
11
+ : Fl_Widget(X, Y, W, H, L),
12
+ ai_loader("./libkhetai.so")
13
+ {
14
+ cell_width = w() / cols;
15
+ cell_height = h() / rows;
16
+ }
17
+
18
+ void GameBoard::draw()
19
+ {
20
+ // background
21
+ fl_color(FL_WHITE);
22
+ fl_rectf(x(), y(), w(), h());
23
+
24
+ // grid lines
25
+ fl_color(FL_BLACK);
26
+
27
+ // vertical lines
28
+ for (int i = 0; i <= cols; ++i)
29
+ {
30
+ int current_x = x() + (cell_width * i);
31
+ fl_line(current_x, y(), current_x, y() + h());
32
+ }
33
+
34
+ // horizontal lines
35
+ for (int j = 0; j <= rows; ++j)
36
+ {
37
+ int current_y = y() + (cell_height * j);
38
+ fl_line(x(), current_y, x() + w(), current_y);
39
+ }
40
+
41
+ // label spaces that only certain colors can move onto
42
+ drawInnerSquares();
43
+
44
+ // highlight selected square
45
+ if (square_selected)
46
+ {
47
+ fl_color(FL_YELLOW);
48
+ fl_rectf((x() + clicked_col * cell_width) + 1, (y() + clicked_row * cell_height) + 1, cell_width - 1, cell_height - 1);
49
+ }
50
+
51
+ // draw laser
52
+ if (laser_active)
53
+ {
54
+ fl_color(FL_RED);
55
+ for (auto &segment : laser_path)
56
+ {
57
+ fl_line(std::get<0>(segment), std::get<1>(segment), std::get<2>(segment), std::get<3>(segment));
58
+ }
59
+ }
60
+
61
+ // draw pieces
62
+ for (int i = 0; i < rows; ++i)
63
+ {
64
+ for (int j = 0; j < cols; ++j)
65
+ {
66
+ int piece_index = i * cols + j;
67
+ if (piece_images[piece_index] != nullptr)
68
+ {
69
+ piece_images[piece_index]->draw(x() + j * cell_width, y() + i * cell_height, cell_width, cell_height);
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ void GameBoard::drawInnerSquares()
76
+ {
77
+ static const Fl_Color SILVER = fl_rgb_color(160, 160, 160);
78
+ static const Fl_Color DARK_RED = fl_rgb_color(178, 34, 34);
79
+ for (int row = 0; row < rows; ++row)
80
+ {
81
+ for (int col = 0; col < cols; ++col)
82
+ {
83
+ int padding_x = cell_width / 6;
84
+ int padding_y = cell_height / 6;
85
+ int square_width = cell_width - (2 * padding_x);
86
+ int square_height = cell_height - (2 * padding_y);
87
+
88
+ switch (move_permissions[row][col])
89
+ {
90
+ case S:
91
+ fl_color(SILVER);
92
+ fl_line_style(FL_SOLID, 1);
93
+ fl_rect(x() + col * cell_width + padding_x,
94
+ y() + row * cell_height + padding_y,
95
+ square_width,
96
+ square_height);
97
+ break;
98
+ case R:
99
+ fl_color(DARK_RED);
100
+ fl_line_style(FL_SOLID, 1);
101
+ fl_rect(x() + col * cell_width + padding_x,
102
+ y() + row * cell_height + padding_y,
103
+ square_width,
104
+ square_height);
105
+ break;
106
+ case B:
107
+ break;
108
+ }
109
+ }
110
+ }
111
+ fl_line_style(0);
112
+ }
113
+
114
+ int GameBoard::handle(int event)
115
+ {
116
+ // std::cout << "EVENT: " event << std::endl;
117
+ switch (event)
118
+ {
119
+ case FL_PUSH:
120
+ {
121
+ int clicked_x = Fl::event_x() - x();
122
+ int clicked_y = Fl::event_y() - y();
123
+
124
+ clicked_col = clicked_x / cell_width;
125
+ clicked_row = clicked_y / cell_height;
126
+
127
+ int clicked_num = (clicked_row * cols) + clicked_col;
128
+
129
+ if (square_selected && square_selected_num == clicked_num)
130
+ {
131
+ square_selected = false;
132
+ square_selected_num = -1;
133
+ }
134
+ else if (square_selected && square_selected_num != clicked_num &&
135
+ squareContainsPiece(square_selected_num) && clicked_num != 0 && clicked_num != 79)
136
+ {
137
+ if (squareContainsPiece(clicked_num))
138
+ {
139
+ swapPieces(clicked_num);
140
+ }
141
+ else
142
+ {
143
+ moveSelectedPiece(clicked_num);
144
+ }
145
+ }
146
+ else if (squareContainsPiece(clicked_num))
147
+ {
148
+ square_selected = true;
149
+ square_selected_num = clicked_num;
150
+ }
151
+
152
+ redraw();
153
+ return 1;
154
+ }
155
+ case FL_KEYUP:
156
+ {
157
+ int key = Fl::event_key();
158
+ int state = Fl::event_state();
159
+
160
+ if (key == 'r')
161
+ {
162
+ resetPieces();
163
+ }
164
+ else if (key == FL_Enter)
165
+ {
166
+ const char *max_time_value = max_time_input->value();
167
+ if (!max_time_value || max_time_value[0] == '\0')
168
+ {
169
+ max_time_input->value("5");
170
+ max_time_value = max_time_input->value();
171
+ }
172
+ int max_time = std::atoi(max_time_value);
173
+
174
+ const char *max_depth_value = max_depth_input->value();
175
+ if (!max_depth_value || max_depth_value[0] == '\0')
176
+ {
177
+ max_depth_input->value("10");
178
+ max_depth_value = max_depth_input->value();
179
+ }
180
+ int max_depth = std::atoi(max_depth_value);
181
+ if (max_depth < 2)
182
+ {
183
+ max_depth_input->value("2");
184
+ max_depth = 2;
185
+ }
186
+
187
+ Move move = call_ai_move(ai_loader, board_pieces, Red, max_depth, max_time);
188
+ int start = get_start(move);
189
+ int end = get_end(move);
190
+ int rotation = get_rotation(move);
191
+
192
+ int start_row, start_col, end_row, end_col;
193
+ get_row_col(start, start_row, start_col);
194
+ get_row_col(end, end_row, end_col);
195
+
196
+ int start_num = (start_row * cols) + start_col;
197
+ int end_num = (end_row * cols) + end_col;
198
+
199
+ square_selected_num = start_num;
200
+
201
+ if (board_pieces[end_row][end_col] == "--")
202
+ {
203
+ moveSelectedPiece(end_num);
204
+ }
205
+ else if (start_num != end_num && board_pieces[end_row][end_col] != "--")
206
+ {
207
+ swapPieces(end_num);
208
+ }
209
+ else if (start_num == end_num && rotation != 0)
210
+ {
211
+ bool clockwise = rotation == 1;
212
+ rotateSelectedPiece(clockwise);
213
+ }
214
+
215
+ fireLaser(RED);
216
+ }
217
+ else if (key == 'k' && (state & FL_SHIFT))
218
+ {
219
+ rebuildReloadKhetAILib();
220
+ return 1;
221
+ }
222
+ else if (square_selected)
223
+ {
224
+ switch (key)
225
+ {
226
+ case FL_Left:
227
+ rotateSelectedPiece(false);
228
+ break;
229
+ case FL_Right:
230
+ rotateSelectedPiece(true);
231
+ break;
232
+ case FL_Delete:
233
+ deletePiece();
234
+ break;
235
+ case ' ':
236
+ if (square_selected_num == 0)
237
+ {
238
+ fireLaser(RED);
239
+ }
240
+ else if (square_selected_num == 79)
241
+ {
242
+ fireLaser(SILVER);
243
+ }
244
+ break;
245
+ }
246
+ }
247
+
248
+ redraw();
249
+ return 1;
250
+ }
251
+ default:
252
+ break;
253
+ }
254
+
255
+ return Fl_Widget::handle(event);
256
+ }
257
+
258
+ void GameBoard::init(const std::vector<std::vector<std::string>> &pieces)
259
+ {
260
+ board_pieces = pieces;
261
+
262
+ for (const std::vector<std::string> &row : board_pieces)
263
+ {
264
+ for (const std::string &piece : row)
265
+ {
266
+ if (piece != "--")
267
+ {
268
+ char piece_type = piece[0];
269
+ int direction = piece[1] - '0';
270
+ std::string filename;
271
+
272
+ filename = getPieceFilename(piece_type, direction);
273
+
274
+ Fl_PNG_Image *orig_image = new Fl_PNG_Image(filename.c_str());
275
+ Fl_Image *resized_image = orig_image->copy(cell_width, cell_height);
276
+ delete orig_image;
277
+ piece_images.push_back(resized_image);
278
+ }
279
+ else
280
+ {
281
+ piece_images.push_back(nullptr);
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ void GameBoard::resetPieces()
288
+ {
289
+ for (Fl_Image *image : piece_images)
290
+ {
291
+ delete image;
292
+ }
293
+ piece_images.clear();
294
+
295
+ square_selected = false;
296
+ square_selected_num = -1;
297
+
298
+ std::vector<std::vector<std::string>> init_board = {
299
+ {"L2", "--", "--", "--", "A2", "X2", "A2", "P1", "--", "--"},
300
+ {"--", "--", "P2", "--", "--", "--", "--", "--", "--", "--"},
301
+ {"--", "--", "--", "p3", "--", "--", "--", "--", "--", "--"},
302
+ {"P0", "--", "p2", "--", "S2", "S3", "--", "P1", "--", "p3"},
303
+ {"P1", "--", "p3", "--", "s1", "s0", "--", "P0", "--", "p2"},
304
+ {"--", "--", "--", "--", "--", "--", "P1", "--", "--", "--"},
305
+ {"--", "--", "--", "--", "--", "--", "--", "p0", "--", "--"},
306
+ {"--", "--", "p3", "a0", "x0", "a0", "--", "--", "--", "l0"}};
307
+
308
+ init(init_board);
309
+ }
310
+
311
+ void GameBoard::deletePiece()
312
+ {
313
+ if (square_selected_num == -1)
314
+ return;
315
+
316
+ if (square_selected_num == 0 || square_selected_num == 79)
317
+ {
318
+ square_selected = false;
319
+ square_selected_num = -1;
320
+ return;
321
+ }
322
+
323
+ delete piece_images[square_selected_num];
324
+ piece_images[square_selected_num] = nullptr;
325
+
326
+ int row = square_selected_num / cols;
327
+ int col = square_selected_num % cols;
328
+
329
+ board_pieces[row][col] = "--";
330
+
331
+ square_selected = false;
332
+ square_selected_num = -1;
333
+ }
334
+
335
+ std::string GameBoard::getPieceFilename(char piece, int direction)
336
+ {
337
+ static const std::string directions[] = {"_n.png", "_e.png", "_s.png", "_w.png"};
338
+
339
+ if (piece == 'X' || piece == 'x')
340
+ {
341
+ return piece_map[piece] + ".png";
342
+ }
343
+
344
+ return piece_map[piece] + directions[direction];
345
+ }
346
+
347
+ void GameBoard::rotateSelectedPiece(bool clockwise)
348
+ {
349
+ if (square_selected_num == -1)
350
+ return;
351
+
352
+ delete piece_images[square_selected_num];
353
+ piece_images[square_selected_num] = nullptr;
354
+
355
+ int row = square_selected_num / cols;
356
+ int col = square_selected_num % cols;
357
+
358
+ std::string current_piece = board_pieces[row][col];
359
+ char piece_type = current_piece[0];
360
+ int current_direction = std::stoi(current_piece.substr(1));
361
+
362
+ int new_direction;
363
+ std::string new_piece;
364
+
365
+ if (clockwise)
366
+ {
367
+ new_direction = rotate_right_map[current_direction];
368
+ }
369
+ else
370
+ {
371
+ new_direction = rotate_left_map[current_direction];
372
+ }
373
+
374
+ new_piece = piece_type + std::to_string(new_direction);
375
+ board_pieces[row][col] = new_piece;
376
+
377
+ std::string filename;
378
+ filename = getPieceFilename(piece_type, new_direction);
379
+ Fl_PNG_Image *orig_image = new Fl_PNG_Image(filename.c_str());
380
+ Fl_Image *resized_image = orig_image->copy(cell_width, cell_height);
381
+ delete orig_image;
382
+ piece_images[square_selected_num] = resized_image;
383
+ }
384
+
385
+ void GameBoard::swapPieces(int swap_square)
386
+ {
387
+ if (square_selected_num == -1)
388
+ return;
389
+
390
+ if (square_selected_num == 0 || square_selected_num == 79 ||
391
+ swap_square == 0 || swap_square == 79)
392
+ {
393
+ square_selected = true;
394
+ square_selected_num = swap_square;
395
+ return;
396
+ }
397
+
398
+ delete piece_images[square_selected_num];
399
+ piece_images[square_selected_num] = nullptr;
400
+
401
+ delete piece_images[swap_square];
402
+ piece_images[swap_square] = nullptr;
403
+
404
+ int first_row = square_selected_num / cols;
405
+ int first_col = square_selected_num % cols;
406
+
407
+ int second_row = swap_square / cols;
408
+ int second_col = swap_square % cols;
409
+
410
+ auto first = board_pieces[first_row][first_col];
411
+ auto second = board_pieces[second_row][second_col];
412
+
413
+ board_pieces[first_row][first_col] = second;
414
+ board_pieces[second_row][second_col] = first;
415
+
416
+ std::string piece_one = board_pieces[first_row][first_col];
417
+ char piece_one_type = piece_one[0];
418
+ int piece_one_direction = std::stoi(piece_one.substr(1));
419
+
420
+ std::string filename_one;
421
+ filename_one = getPieceFilename(piece_one_type, piece_one_direction);
422
+ Fl_PNG_Image *orig_image_one = new Fl_PNG_Image(filename_one.c_str());
423
+ Fl_Image *resized_image_one = orig_image_one->copy(cell_width, cell_height);
424
+ delete orig_image_one;
425
+ piece_images[square_selected_num] = resized_image_one;
426
+
427
+ std::string piece_two = board_pieces[second_row][second_col];
428
+ char piece_two_type = piece_two[0];
429
+ int piece_two_direction = std::stoi(piece_two.substr(1));
430
+
431
+ std::string filename_two;
432
+ filename_two = getPieceFilename(piece_two_type, piece_two_direction);
433
+ Fl_PNG_Image *orig_image_two = new Fl_PNG_Image(filename_two.c_str());
434
+ Fl_Image *resized_image_two = orig_image_two->copy(cell_width, cell_height);
435
+ delete orig_image_two;
436
+ piece_images[swap_square] = resized_image_two;
437
+
438
+ square_selected = false;
439
+ square_selected_num = -1;
440
+ }
441
+
442
+ void GameBoard::moveSelectedPiece(int end_square)
443
+ {
444
+ if (square_selected_num == -1)
445
+ return;
446
+
447
+ if (square_selected_num == 0 || square_selected_num == 79)
448
+ {
449
+ square_selected = false;
450
+ square_selected_num = -1;
451
+ return;
452
+ }
453
+
454
+ delete piece_images[square_selected_num];
455
+ piece_images[square_selected_num] = nullptr;
456
+
457
+ int start_row = square_selected_num / cols;
458
+ int start_col = square_selected_num % cols;
459
+
460
+ int end_row = end_square / cols;
461
+ int end_col = end_square % cols;
462
+
463
+ board_pieces[end_row][end_col] = board_pieces[start_row][start_col];
464
+ board_pieces[start_row][start_col] = "--";
465
+
466
+ std::string current_piece = board_pieces[end_row][end_col];
467
+ char piece_type = current_piece[0];
468
+ int piece_direction = std::stoi(current_piece.substr(1));
469
+
470
+ std::string filename;
471
+ filename = getPieceFilename(piece_type, piece_direction);
472
+ Fl_PNG_Image *orig_image = new Fl_PNG_Image(filename.c_str());
473
+ Fl_Image *resized_image = orig_image->copy(cell_width, cell_height);
474
+ delete orig_image;
475
+ piece_images[end_square] = resized_image;
476
+
477
+ square_selected = false;
478
+ square_selected_num = -1;
479
+ }
480
+
481
+ void GameBoard::fireLaser(Color color)
482
+ {
483
+ laser_active = true;
484
+ laser_path.clear();
485
+
486
+ int start_x;
487
+ int start_y;
488
+ int end_x;
489
+ int end_y;
490
+
491
+ if (color == RED)
492
+ {
493
+ std::string piece_str = board_pieces[0][0];
494
+ auto [piece_type, piece_orientation] = getPieceTypeAndOrientation(piece_str);
495
+
496
+ start_x = x() + (cell_width / 2);
497
+ start_y = y() + (cell_height / 2);
498
+
499
+ if (piece_orientation == ORIENT_EAST)
500
+ {
501
+
502
+ end_x = start_x + laser_step;
503
+ end_y = start_y;
504
+ laser_direction = EAST;
505
+ laser_square_row = 0;
506
+ laser_square_col = 0;
507
+ }
508
+ else if (piece_orientation == ORIENT_SOUTH)
509
+ {
510
+
511
+ end_x = start_x;
512
+ end_y = start_y + laser_step;
513
+ laser_direction = SOUTH;
514
+ laser_square_row = 0;
515
+ laser_square_col = 0;
516
+ }
517
+ else
518
+ {
519
+ laser_active = false;
520
+ return;
521
+ }
522
+ }
523
+ else if (color == SILVER)
524
+ {
525
+ int current_row = rows - 1;
526
+ int current_col = cols - 1;
527
+ std::string piece_str = board_pieces[current_row][current_col];
528
+ auto [piece_type, piece_orientation] = getPieceTypeAndOrientation(piece_str);
529
+
530
+ if (piece_orientation == ORIENT_WEST)
531
+ {
532
+ start_x = x() + (current_col * cell_width) + (cell_width / 2);
533
+ start_y = y() + (current_row * cell_height) + (cell_height / 2);
534
+ end_x = start_x - laser_step;
535
+ end_y = start_y;
536
+ laser_direction = WEST;
537
+ laser_square_row = current_row;
538
+ laser_square_col = current_col;
539
+ }
540
+ else if (piece_orientation == ORIENT_NORTH)
541
+ {
542
+ start_x = x() + (current_col * cell_width) + (cell_width / 2);
543
+ start_y = y() + (current_row * cell_height) + (cell_height / 2);
544
+ end_x = start_x;
545
+ end_y = start_y - laser_step;
546
+ laser_direction = NORTH;
547
+ laser_square_row = current_row;
548
+ laser_square_col = current_col;
549
+ }
550
+ else
551
+ {
552
+ laser_active = false;
553
+ return;
554
+ }
555
+ }
556
+
557
+ calculateLaserPathSquares();
558
+ // print path:
559
+ std::cout << "laser_path_squares = [";
560
+ for (size_t i = 0; i < laser_path_squares.size(); ++i)
561
+ {
562
+ std::apply([](auto &&...args)
563
+ {
564
+ std::cout << '(';
565
+ ((std::cout << args << ", "), ...);
566
+ std::cout << '\b' << '\b' << ')'; },
567
+ laser_path_squares[i]);
568
+ if (i != laser_path_squares.size() - 1)
569
+ {
570
+ std::cout << ", ";
571
+ }
572
+ }
573
+ std::cout << "]" << std::endl;
574
+
575
+ l_idx = 0;
576
+
577
+ // add the segment to the path
578
+ laser_path.push_back(std::make_tuple(start_x, start_y, end_x, end_y));
579
+ laser_y = end_y;
580
+ laser_x = end_x;
581
+
582
+ redraw();
583
+ Fl::add_timeout(0.01, laser_timer_cb, this);
584
+ }
585
+
586
+ void GameBoard::updateLaserPosition()
587
+ {
588
+ if (laser_y >= y() + (rows * cell_height) || laser_y <= y() || laser_x >= x() + (cols * cell_width) || laser_x <= x())
589
+ {
590
+ laser_active = false;
591
+ laser_path.clear();
592
+ redraw();
593
+ return;
594
+ }
595
+
596
+ std::tuple<int, int, int, int> current_segment = laser_path_squares[l_idx];
597
+ int goal_row = std::get<2>(current_segment);
598
+ int goal_col = std::get<3>(current_segment);
599
+
600
+ int goal_x = x() + (goal_col * cell_width) + (cell_width / 2);
601
+ int goal_y = y() + (goal_row * cell_height) + (cell_height / 2);
602
+
603
+ // if the laser steps beyond the middle of the square,
604
+ // set to exactly the middle
605
+ if (((laser_direction == NORTH && laser_y < goal_y) ||
606
+ (laser_direction == SOUTH && laser_y > goal_y) ||
607
+ (laser_direction == EAST && laser_x > goal_x) ||
608
+ (laser_direction == WEST && laser_x < goal_x)) &&
609
+ (l_idx < laser_path_squares.size()))
610
+ {
611
+ laser_x = goal_x;
612
+ laser_y = goal_y;
613
+ }
614
+
615
+ // determine next direction if we are at the middle of a square
616
+ if (laser_x == goal_x && laser_y == goal_y)
617
+ {
618
+ if (l_idx >= laser_path_squares.size() - 1)
619
+ {
620
+ laser_active = false;
621
+ laser_path.clear();
622
+ redraw();
623
+ return;
624
+ }
625
+
626
+ l_idx++;
627
+ current_segment = laser_path_squares[l_idx];
628
+ auto [cur_row, cur_col, end_row, end_col] = current_segment;
629
+
630
+ if (cur_row == end_row)
631
+ {
632
+ laser_direction = (cur_col < end_col) ? EAST : WEST;
633
+ }
634
+ else
635
+ {
636
+ laser_direction = (cur_row < end_row) ? SOUTH : NORTH;
637
+ }
638
+ }
639
+
640
+ switch (laser_direction)
641
+ {
642
+ case NORTH:
643
+ laser_y -= laser_step;
644
+ break;
645
+ case SOUTH:
646
+ laser_y += laser_step;
647
+ break;
648
+ case EAST:
649
+ laser_x += laser_step;
650
+ break;
651
+ case WEST:
652
+ laser_x -= laser_step;
653
+ break;
654
+ }
655
+
656
+ auto last_segment = laser_path.back();
657
+ int start_x = std::get<2>(last_segment);
658
+ int start_y = std::get<3>(last_segment);
659
+ int end_x = laser_x;
660
+ int end_y = laser_y;
661
+
662
+ laser_path.push_back(std::make_tuple(start_x, start_y, end_x, end_y));
663
+ redraw();
664
+ }
665
+
666
+ void GameBoard::laser_timer_cb(void *data)
667
+ {
668
+ GameBoard *gb = static_cast<GameBoard *>(data);
669
+ if (gb->laser_active)
670
+ {
671
+ gb->updateLaserPosition();
672
+ Fl::repeat_timeout(0.01, laser_timer_cb, data);
673
+ }
674
+ }
675
+
676
+ void GameBoard::calculateLaserPathSquares()
677
+ {
678
+ laser_path_squares.clear();
679
+
680
+ int current_row = laser_square_row;
681
+ int current_col = laser_square_col;
682
+ LaserDirection direction = laser_direction;
683
+
684
+ bool calculating = true;
685
+ while (calculating)
686
+ {
687
+ int start_row = current_row;
688
+ int start_col = current_col;
689
+ int end_row = current_row;
690
+ int end_col = current_col;
691
+
692
+ switch (direction)
693
+ {
694
+ case NORTH:
695
+ end_row -= 1;
696
+ break;
697
+ case EAST:
698
+ end_col += 1;
699
+ break;
700
+ case SOUTH:
701
+ end_row += 1;
702
+ break;
703
+ case WEST:
704
+ end_col -= 1;
705
+ break;
706
+ }
707
+
708
+ // out of bounds?
709
+ if (end_row < 0 || end_row >= rows || end_col < 0 || end_col >= cols)
710
+ {
711
+ break;
712
+ }
713
+
714
+ laser_path_squares.push_back(std::make_tuple(start_row, start_col, end_row, end_col));
715
+
716
+ // determine next direction...
717
+ current_row = end_row;
718
+ current_col = end_col;
719
+
720
+ std::string piece_str = board_pieces[current_row][current_col];
721
+
722
+ if (piece_str == "--")
723
+ {
724
+ continue;
725
+ }
726
+
727
+ // next square contains a piece... determine what happens next...
728
+ auto [piece_type, piece_orientation] = getPieceTypeAndOrientation(piece_str);
729
+ auto reflection_result = reflections_map[direction][piece_type][piece_orientation];
730
+
731
+ switch (reflection_result)
732
+ {
733
+ case RESULT_ABSORBED:
734
+ calculating = false;
735
+ break;
736
+ case RESULT_DEAD:
737
+ remove_piece = true;
738
+ remove_row = current_row;
739
+ remove_col = current_col;
740
+ calculating = false;
741
+ break;
742
+ case RESULT_EAST:
743
+ direction = EAST;
744
+ break;
745
+ case RESULT_WEST:
746
+ direction = WEST;
747
+ break;
748
+ case RESULT_SOUTH:
749
+ direction = SOUTH;
750
+ break;
751
+ case RESULT_NORTH:
752
+ direction = NORTH;
753
+ break;
754
+ }
755
+ }
756
+ }
757
+
758
+ std::pair<GameBoard::PieceType, GameBoard::PieceOrientation> GameBoard::getPieceTypeAndOrientation(const std::string &piece_str)
759
+ {
760
+
761
+ char piece_char = std::toupper(piece_str[0]);
762
+ char orient_char = piece_str[1];
763
+
764
+ PieceType piece_type;
765
+ PieceOrientation piece_orientation;
766
+
767
+ switch (piece_char)
768
+ {
769
+ case 'P':
770
+ piece_type = PYRAMID;
771
+ break;
772
+ case 'A':
773
+ piece_type = ANUBIS;
774
+ break;
775
+ case 'S':
776
+ piece_type = SCARAB;
777
+ break;
778
+ case 'X':
779
+ piece_type = PHARAOH;
780
+ break;
781
+ case 'L':
782
+ piece_type = SPHINX;
783
+ break;
784
+ default:
785
+ throw std::invalid_argument("Invalid piece type");
786
+ }
787
+
788
+ switch (orient_char)
789
+ {
790
+ case '0':
791
+ piece_orientation = ORIENT_NORTH;
792
+ break;
793
+ case '1':
794
+ piece_orientation = ORIENT_EAST;
795
+ break;
796
+ case '2':
797
+ piece_orientation = ORIENT_SOUTH;
798
+ break;
799
+ case '3':
800
+ piece_orientation = ORIENT_WEST;
801
+ break;
802
+ default:
803
+ throw std::invalid_argument("Invalid piece orientation");
804
+ }
805
+
806
+ return std::make_pair(piece_type, piece_orientation);
807
+ }
808
+
809
+ bool GameBoard::squareContainsPiece(int square_num)
810
+ {
811
+ int row = square_num / cols;
812
+ int col = square_num % cols;
813
+ return board_pieces[row][col] != "--";
814
+ }
815
+
816
+ void GameBoard::rebuildReloadKhetAILib()
817
+ {
818
+ std::cout << "\nRebuilding and reloading KhetAI lib..." << std::endl;
819
+
820
+ int build_result = system("./build_khetai.sh");
821
+ if (build_result != 0)
822
+ {
823
+ std::cerr << "Failed to rebuild KhetAI lib" << std::endl;
824
+ return;
825
+ }
826
+
827
+ try
828
+ {
829
+ ai_loader.reload_library("./libkhetai.so");
830
+ std::cout << "KhetAI lib reloaded successfully" << std::endl;
831
+ }
832
+ catch (const std::runtime_error &e)
833
+ {
834
+ std::cerr << "Failed to reload KhetAI lib: " << e.what() << std::endl;
835
+ }
836
+ }
837
+
838
+ std::unordered_map<char, std::string> GameBoard::piece_map = {
839
+ {'L', "assets/laser_red"},
840
+ {'A', "assets/anubis_red"},
841
+ {'X', "assets/pharaoh_red"},
842
+ {'P', "assets/pyramid_red"},
843
+ {'S', "assets/scarab_red"},
844
+ {'l', "assets/laser_silver"},
845
+ {'a', "assets/anubis_silver"},
846
+ {'x', "assets/pharaoh_silver"},
847
+ {'p', "assets/pyramid_silver"},
848
+ {'s', "assets/scarab_silver"}};
849
+
850
+ std::unordered_map<int, int> GameBoard::rotate_left_map = {
851
+ {0, 3}, {3, 2}, {2, 1}, {1, 0}};
852
+
853
+ std::unordered_map<int, int> GameBoard::rotate_right_map = {
854
+ {0, 1}, {1, 2}, {2, 3}, {3, 0}};
855
+
856
+ const GameBoard::MovePermission GameBoard::move_permissions[8][10] = {
857
+ {B, S, B, B, B, B, B, B, R, S},
858
+ {R, B, B, B, B, B, B, B, B, S},
859
+ {R, B, B, B, B, B, B, B, B, S},
860
+ {R, B, B, B, B, B, B, B, B, S},
861
+ {R, B, B, B, B, B, B, B, B, S},
862
+ {R, B, B, B, B, B, B, B, B, S},
863
+ {R, B, B, B, B, B, B, B, B, S},
864
+ {R, S, B, B, B, B, B, B, R, B}};
865
+
866
+ // clang-format off
867
+ std::unordered_map<GameBoard::LaserDirection, std::unordered_map<GameBoard::PieceType, std::unordered_map<GameBoard::PieceOrientation, GameBoard::ReflectionResult>>> GameBoard::reflections_map = {
868
+ {NORTH, {
869
+ {ANUBIS, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_ABSORBED}, {ORIENT_WEST, RESULT_DEAD}}},
870
+ {PYRAMID, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_EAST}, {ORIENT_SOUTH, RESULT_WEST}, {ORIENT_WEST, RESULT_DEAD}}},
871
+ {SCARAB, {{ORIENT_NORTH, RESULT_WEST}, {ORIENT_EAST, RESULT_EAST}, {ORIENT_SOUTH, RESULT_WEST}, {ORIENT_WEST, RESULT_EAST}}},
872
+ {PHARAOH, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
873
+ {SPHINX, {{ORIENT_NORTH, RESULT_ABSORBED}, {ORIENT_EAST, RESULT_ABSORBED}, {ORIENT_SOUTH, RESULT_ABSORBED}, {ORIENT_WEST, RESULT_ABSORBED}}}
874
+ }},
875
+ {EAST, {
876
+ {ANUBIS, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_ABSORBED}}},
877
+ {PYRAMID, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_SOUTH}, {ORIENT_WEST, RESULT_NORTH}}},
878
+ {SCARAB, {{ORIENT_NORTH, RESULT_SOUTH}, {ORIENT_EAST, RESULT_NORTH}, {ORIENT_SOUTH, RESULT_SOUTH}, {ORIENT_WEST, RESULT_NORTH}}},
879
+ {PHARAOH, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
880
+ {SPHINX, {{ORIENT_NORTH, RESULT_ABSORBED}, {ORIENT_EAST, RESULT_ABSORBED}, {ORIENT_SOUTH, RESULT_ABSORBED}, {ORIENT_WEST, RESULT_ABSORBED}}}
881
+ }},
882
+ {SOUTH, {
883
+ {ANUBIS, {{ORIENT_NORTH, RESULT_ABSORBED}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
884
+ {PYRAMID, {{ORIENT_NORTH, RESULT_EAST}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_WEST}}},
885
+ {SCARAB, {{ORIENT_NORTH, RESULT_EAST}, {ORIENT_EAST, RESULT_WEST}, {ORIENT_SOUTH, RESULT_EAST}, {ORIENT_WEST, RESULT_WEST}}},
886
+ {PHARAOH, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
887
+ {SPHINX, {{ORIENT_NORTH, RESULT_ABSORBED}, {ORIENT_EAST, RESULT_ABSORBED}, {ORIENT_SOUTH, RESULT_ABSORBED}, {ORIENT_WEST, RESULT_ABSORBED}}}
888
+ }},
889
+ {WEST, {
890
+ {ANUBIS, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_ABSORBED}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
891
+ {PYRAMID, {{ORIENT_NORTH, RESULT_NORTH}, {ORIENT_EAST, RESULT_SOUTH}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
892
+ {SCARAB, {{ORIENT_NORTH, RESULT_NORTH}, {ORIENT_EAST, RESULT_SOUTH}, {ORIENT_SOUTH, RESULT_NORTH}, {ORIENT_WEST, RESULT_SOUTH}}},
893
+ {PHARAOH, {{ORIENT_NORTH, RESULT_DEAD}, {ORIENT_EAST, RESULT_DEAD}, {ORIENT_SOUTH, RESULT_DEAD}, {ORIENT_WEST, RESULT_DEAD}}},
894
+ {SPHINX, {{ORIENT_NORTH, RESULT_ABSORBED}, {ORIENT_EAST, RESULT_ABSORBED}, {ORIENT_SOUTH, RESULT_ABSORBED}, {ORIENT_WEST, RESULT_ABSORBED}}}
895
+ }}
896
+ };