khetai 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -5
  3. data/Gemfile.lock +1 -1
  4. data/README.md +19 -11
  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 +22 -11
  56. data/ext/khetai/khetai_lib.h +35 -8
  57. data/lib/khetai/version.rb +1 -1
  58. metadata +49 -2
@@ -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
+ };