khetai 0.1.6

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '09db06457292f856f5e69a7f1af7171cc3d1fcc39b4bf53638ed34607fbd35d0'
4
+ data.tar.gz: 5a11e03e0a5fbfb4f6189fe35e5e75905b57cdc7ec6efcae50b2a5d69cc56be3
5
+ SHA512:
6
+ metadata.gz: c2595304f50a6ccb71b158f84377b4c10c747284081a1c4ac24f5fccbd42dc69ea074e7476626432bdd29e5519932740f577c9bb25dfe70e6421975993813fcf
7
+ data.tar.gz: 277cc078b123efae38ddc9731eca386d7d11adbb063f8660cbfb2acccd47d5d4f109b122818100f0f5fa526f238728b72e6812d04f02fa16082f8189798d7384
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .vscode
10
+ *.so
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in khetai.gemspec
4
+ gemspec
5
+
6
+ gem "rake"
7
+ gem "rake-compiler"
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ khetai (0.1.6)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (13.0.6)
10
+ rake-compiler (1.1.9)
11
+ rake
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ khetai!
18
+ rake
19
+ rake-compiler
20
+
21
+ BUNDLED WITH
22
+ 2.1.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 jkugs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # KhetAI
2
+
3
+ ## Usage
4
+ ```
5
+ require 'khetai'
6
+ move = KhetAI.move(board, whose_turn, max_search_depth, max_search_time)
7
+ ```
8
+
9
+ ## Example
10
+ ```
11
+ require 'khetai'
12
+
13
+ # initial board setup:
14
+ # A = anubis, P = pyramid, S = scarab, X = pharaoh, L = sphinx
15
+ # capital letters = red, lowercase letters = silver
16
+ # 0 = north, 1 = east, 2 = south, 3 = west
17
+ board = ["--", "--", "--", "--", "--", "--", "--", "--", "--", "--", "--", "--",
18
+ "--", "L2", "--", "--", "--", "A2", "X2", "A2", "P1", "--", "--", "--",
19
+ "--", "--", "--", "P2", "--", "--", "--", "--", "--", "--", "--", "--",
20
+ "--", "--", "--", "--", "p3", "--", "--", "--", "--", "--", "--", "--",
21
+ "--", "P0", "--", "p2", "--", "S2", "S3", "--", "P1", "--", "p3", "--",
22
+ "--", "P1", "--", "p3", "--", "s1", "s0", "--", "P0", "--", "p2", "--",
23
+ "--", "--", "--", "--", "--", "--", "--", "P1", "--", "--", "--", "--",
24
+ "--", "--", "--", "--", "--", "--", "--", "--", "p0", "--", "--", "--",
25
+ "--", "--", "--", "p3", "a0", "x0", "a0", "--", "--", "--", "l0", "--",
26
+ "--", "--", "--", "--", "--", "--", "--", "--", "--", "--", "--", "--"]
27
+
28
+ whose_turn = 1 # silver = 0, red = 1
29
+ max_search_depth = 25 # must be between 2 and 25
30
+ max_search_time = 5 # max search time in seconds
31
+
32
+ # returns [start_index, end_index, rotation]
33
+ move = KhetAI.move(board, whose_turn, max_search_depth, max_search_time)
34
+
35
+ # move[0] = start index
36
+ # move[1] = end index
37
+ # move[2] = rotation (1, -1, 0) (clockwise, anticlockwise, none)
38
+ ```
39
+
40
+ ## Build and Deploy Commands
41
+ ```
42
+ bundle exec rake compile
43
+ bundle ecec rake build
44
+ bundle exec rake release
45
+
46
+ gem push pkg/<gem>
47
+ ```
@@ -0,0 +1,40 @@
1
+ # Khetai
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/khetai`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'khetai'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install khetai
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/khetai.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
3
+
4
+ require "rake"
5
+ require "rake/extensiontask"
6
+ Rake::ExtensionTask.new "khetai" do |ext|
7
+ ext.lib_dir = "lib/khetai"
8
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "khetai"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('khetai/khetai')
@@ -0,0 +1,51 @@
1
+ #include <ruby.h>
2
+ #include <stdlib.h>
3
+ #include <time.h>
4
+ #include "khetai_lib.h"
5
+
6
+ time_t start_time;
7
+ int max_time;
8
+
9
+ VALUE move(VALUE self, VALUE board_array, VALUE player, VALUE max_depth, VALUE _max_time)
10
+ {
11
+ srand((unsigned)time(NULL));
12
+
13
+ char *init_board[120];
14
+ unsigned int array_size = (unsigned int)RARRAY_LEN(board_array);
15
+ for (unsigned int i = 0; i < array_size; i++)
16
+ {
17
+ VALUE square = rb_ary_entry(board_array, i);
18
+ init_board[i] = StringValueCStr(square);
19
+ }
20
+
21
+ reset_undo();
22
+ init_zobrist();
23
+ setup_board(init_board);
24
+
25
+ start_time = time(NULL);
26
+ max_time = NUM2INT(_max_time);
27
+
28
+ int depth = 1;
29
+ Move best_move = (Move)0;
30
+ Move current_move = (Move)0;
31
+ while ((time(NULL) - start_time < max_time) && (depth <= NUM2INT(max_depth)))
32
+ {
33
+ best_move = current_move;
34
+ current_move = alphabeta_root(depth, NUM2INT(player));
35
+ depth++;
36
+ }
37
+ make_move(best_move);
38
+
39
+ VALUE out = rb_ary_new2(3);
40
+ rb_ary_store(out, 0, INT2NUM(get_start(best_move)));
41
+ rb_ary_store(out, 1, INT2NUM(get_end(best_move)));
42
+ rb_ary_store(out, 2, INT2NUM(get_rotation(best_move)));
43
+
44
+ return out;
45
+ }
46
+
47
+ void Init_khetai()
48
+ {
49
+ VALUE KhetAI = rb_define_module("KhetAI");
50
+ rb_define_singleton_method(KhetAI, "move", move, 4);
51
+ }
@@ -0,0 +1,578 @@
1
+ #include <stdio.h>
2
+ #include <ctype.h>
3
+ #include <stdlib.h>
4
+ #include <time.h>
5
+ #include "khetai_lib.h"
6
+
7
+ Square board[120] = {0};
8
+ enum Player whose_turn;
9
+
10
+ Move undo_moves[MAX_DEPTH] = {0};
11
+ int undo_capture_indices[MAX_DEPTH] = {0};
12
+ Square undo_capture_squares[MAX_DEPTH] = {0};
13
+
14
+ HashEntry table[TABLE_SIZE] = {0};
15
+ uint64_t hashes[MAX_DEPTH] = {0};
16
+ uint64_t keys[0xFF][120] = {0};
17
+ uint64_t turn_key = 0;
18
+
19
+ int undo_index = 0;
20
+ int move_num = 0;
21
+ bool checkmate = false;
22
+
23
+ Move alphabeta_root(int depth, enum Player player)
24
+ {
25
+ whose_turn = player;
26
+ int best_score = -MAX_SCORE;
27
+ Move best_move = (Move)0;
28
+ int alpha = -MAX_SCORE;
29
+ int beta = MAX_SCORE;
30
+ Move valid_moves[NUM_VALID_MOVES] = {0};
31
+ int vi = 0;
32
+ find_valid_moves(valid_moves, &vi);
33
+ for (int i = 0; i < NUM_VALID_MOVES; i++)
34
+ {
35
+ if (valid_moves[i] == 0)
36
+ break;
37
+ make_move(valid_moves[i]);
38
+ int score = -alphabeta(depth - 1, opposite_player(player), -beta, -alpha);
39
+ undo_move();
40
+ whose_turn = player;
41
+ if (score > best_score)
42
+ {
43
+ best_score = score;
44
+ best_move = valid_moves[i];
45
+ }
46
+ if (best_score > alpha)
47
+ alpha = best_score;
48
+ if (alpha >= beta)
49
+ break;
50
+ }
51
+ return best_move;
52
+ }
53
+
54
+ int alphabeta(int depth, enum Player player, int alpha, int beta)
55
+ {
56
+ whose_turn = player;
57
+ if (depth == 0 || checkmate)
58
+ {
59
+ return player == Red ? calculate_score() : -calculate_score();
60
+ }
61
+
62
+ int alpha_orig = alpha;
63
+ Move valid_moves[NUM_VALID_MOVES] = {0};
64
+ int vi = 0;
65
+ HashEntry *entry = search_table(hashes[move_num]);
66
+ if (entry->key == hashes[move_num] && entry->depth >= depth)
67
+ {
68
+ if (entry->flag == EXACT)
69
+ return entry->score;
70
+ else if (entry->flag == ALPHA)
71
+ {
72
+ if (entry->score > alpha)
73
+ alpha = entry->score;
74
+ }
75
+ else
76
+ {
77
+ if (entry->score < beta)
78
+ beta = entry->score;
79
+ }
80
+
81
+ if (alpha >= beta)
82
+ return entry->score;
83
+
84
+ if (is_move_legal(entry->move))
85
+ valid_moves[vi++] = entry->move;
86
+ }
87
+
88
+ find_valid_moves(valid_moves, &vi);
89
+ int best_score = -MAX_SCORE;
90
+ Move best_move = (Move)0;
91
+ for (int i = 0; (i < NUM_VALID_MOVES && (time(NULL) - start_time < max_time)); i++)
92
+ {
93
+ if (valid_moves[i] == 0)
94
+ break;
95
+ make_move(valid_moves[i]);
96
+ int score = -alphabeta(depth - 1, opposite_player(player), -beta, -alpha);
97
+ undo_move();
98
+ whose_turn = player;
99
+ if (score > best_score)
100
+ {
101
+ best_score = score;
102
+ best_move = valid_moves[i];
103
+ }
104
+ if (best_score > alpha)
105
+ alpha = best_score;
106
+ if (alpha >= beta)
107
+ break;
108
+ }
109
+
110
+ int flag = EXACT;
111
+ if (best_score <= alpha_orig)
112
+ flag = BETA;
113
+ else if (best_score >= beta)
114
+ flag = ALPHA;
115
+
116
+ insert_table(hashes[move_num], depth, flag, best_score, best_move);
117
+ return best_score;
118
+ }
119
+
120
+ void insert_table(uint64_t key, int depth, int flag, int score, Move move)
121
+ {
122
+ HashEntry *entry = search_table(key);
123
+ if (entry->key != 0)
124
+ {
125
+ if (depth > entry->depth)
126
+ {
127
+ entry->key = key;
128
+ entry->depth = depth;
129
+ entry->flag = flag;
130
+ entry->score = score;
131
+ entry->move = move;
132
+ }
133
+ }
134
+ else
135
+ {
136
+ entry->key = key;
137
+ entry->depth = depth;
138
+ entry->flag = flag;
139
+ entry->score = score;
140
+ entry->move = move;
141
+ }
142
+ }
143
+
144
+ int calculate_score()
145
+ {
146
+ int score = 0;
147
+ int anubis_score = 500;
148
+ int scarab_score = 750;
149
+ int pyramid_score = 1000;
150
+ int pharaoh_score = 100000;
151
+ for (int i = 0; i < 120; i++)
152
+ {
153
+ if (board[i] > 0)
154
+ {
155
+ int value = 0;
156
+ switch (get_piece(board[i]))
157
+ {
158
+ case Anubis:
159
+ value += anubis_score;
160
+ break;
161
+ case Pyramid:
162
+ value += pyramid_score;
163
+ break;
164
+ case Scarab:
165
+ value += scarab_score;
166
+ break;
167
+ case Pharaoh:
168
+ value += pharaoh_score;
169
+ break;
170
+ default:
171
+ break;
172
+ }
173
+ score += get_owner(board[i]) == Red ? value : -value;
174
+ }
175
+ }
176
+ return score += rand() % 100;
177
+ }
178
+
179
+ void make_move(Move move)
180
+ {
181
+ uint64_t hash = hashes[move_num++];
182
+
183
+ int start = get_start(move);
184
+ // remove starting piece
185
+ hash ^= keys[board[start]][start];
186
+ int end = get_end(move);
187
+ int rotation = get_rotation(move);
188
+
189
+ if (rotation != 0)
190
+ {
191
+ board[start] = rotate(board[start], rotation);
192
+ // add starting piece back with rotation
193
+ hash ^= keys[board[start]][start];
194
+ }
195
+ else
196
+ {
197
+ // remove ending piece if swapping
198
+ if (is_piece(board[end]))
199
+ hash ^= keys[board[end]][end];
200
+
201
+ Square moving_piece = board[start];
202
+ board[start] = board[end];
203
+ board[end] = moving_piece;
204
+ // add starting piece to end location
205
+ hash ^= keys[board[end]][end];
206
+
207
+ // add ending piece to start position if swapping
208
+ if (is_piece(board[start]))
209
+ hash ^= keys[board[start]][start];
210
+ }
211
+
212
+ undo_moves[undo_index] = new_move(end, start, -rotation);
213
+
214
+ fire_laser(&hash);
215
+ hash ^= turn_key;
216
+
217
+ hashes[move_num] = hash;
218
+ undo_index++;
219
+ }
220
+
221
+ void fire_laser(uint64_t *hash)
222
+ {
223
+ int i = sphinx_loc[whose_turn];
224
+ int laser_dir = get_orientation(board[i]);
225
+ bool traversing = true;
226
+ while (traversing)
227
+ {
228
+ i = i + directions[laser_dir];
229
+ if (i >= 0 && i < 120 && on_board[i] == 1)
230
+ {
231
+ if (board[i] > 0)
232
+ {
233
+ int piece = get_piece(board[i]) - 1;
234
+ int orientation = get_orientation(board[i]);
235
+ int result = reflections[laser_dir][piece][orientation];
236
+ if (result == Dead)
237
+ {
238
+ if (get_piece(board[i]) == Pharaoh)
239
+ checkmate = true;
240
+ *hash ^= keys[board[i]][i];
241
+ undo_capture_indices[undo_index] = i;
242
+ undo_capture_squares[undo_index] = board[i];
243
+ board[i] = (Square)0;
244
+ traversing = false;
245
+ }
246
+ else if (result == Absorbed)
247
+ {
248
+ traversing = false;
249
+ }
250
+ else
251
+ {
252
+ laser_dir = result;
253
+ }
254
+ }
255
+ }
256
+ else
257
+ {
258
+ traversing = false;
259
+ }
260
+ }
261
+ }
262
+
263
+ void undo_move()
264
+ {
265
+ move_num--;
266
+ undo_index--;
267
+
268
+ Square captured = (Square)undo_capture_squares[undo_index];
269
+ undo_capture_squares[undo_index] = 0;
270
+ if (captured > 0)
271
+ {
272
+ board[undo_capture_indices[undo_index]] = captured;
273
+ undo_capture_indices[undo_index] = 0;
274
+ }
275
+
276
+ Move move = undo_moves[undo_index];
277
+ undo_moves[undo_index] = 0;
278
+ int start = get_start(move);
279
+ int end = get_end(move);
280
+ int rotation = get_rotation(move);
281
+
282
+ if (rotation != 0)
283
+ {
284
+ board[start] = rotate(board[start], rotation);
285
+ }
286
+ else
287
+ {
288
+ Square moving_piece = board[start];
289
+ board[start] = board[end];
290
+ board[end] = moving_piece;
291
+ }
292
+ checkmate = false;
293
+ }
294
+
295
+ void find_valid_moves(Move *valid_moves, int *vi)
296
+ {
297
+ for (int i = 0; i < 120; i++)
298
+ {
299
+ Square s = board[i];
300
+ enum Player piece_color = get_owner(s);
301
+ if (is_piece(s) && piece_color == whose_turn)
302
+ {
303
+ enum Piece piece = get_piece(s);
304
+ switch (piece)
305
+ {
306
+ case Anubis:
307
+ find_valid_anubis_pyramid_moves(i, valid_moves, vi);
308
+ break;
309
+ case Pyramid:
310
+ find_valid_anubis_pyramid_moves(i, valid_moves, vi);
311
+ break;
312
+ case Scarab:
313
+ find_valid_scarab_moves(i, valid_moves, vi);
314
+ break;
315
+ case Pharaoh:
316
+ find_valid_pharaoh_moves(i, valid_moves, vi);
317
+ break;
318
+ case Sphinx:
319
+ find_valid_sphinx_moves(i, valid_moves, vi);
320
+ break;
321
+ default:
322
+ break;
323
+ }
324
+ }
325
+ }
326
+ }
327
+
328
+ void find_valid_anubis_pyramid_moves(int i, Move *valid_moves, int *vi)
329
+ {
330
+ for (int j = 0; j < 8; j++)
331
+ {
332
+ int dest = i + directions[j];
333
+ if (!is_piece(board[dest]) && can_move[whose_turn][dest])
334
+ {
335
+ valid_moves[(*vi)++] = new_move(i, dest, 0);
336
+ }
337
+ }
338
+ for (int j = 0; j < 2; j++)
339
+ {
340
+ valid_moves[(*vi)++] = new_move(i, i, rotations[j]);
341
+ }
342
+ }
343
+
344
+ void find_valid_scarab_moves(int i, Move *valid_moves, int *vi)
345
+ {
346
+ for (int j = 0; j < 8; j++)
347
+ {
348
+ int dest = i + directions[j];
349
+ if (can_move[whose_turn][dest])
350
+ {
351
+ if (is_piece(board[dest]))
352
+ {
353
+ if (get_piece(board[dest]) == Anubis || get_piece(board[dest]) == Pyramid)
354
+ {
355
+ valid_moves[(*vi)++] = new_move(i, dest, 0);
356
+ }
357
+ }
358
+ else
359
+ {
360
+ valid_moves[(*vi)++] = new_move(i, dest, 0);
361
+ }
362
+ }
363
+ }
364
+ for (int j = 0; j < 2; j++)
365
+ {
366
+ valid_moves[(*vi)++] = new_move(i, i, rotations[j]);
367
+ }
368
+ }
369
+
370
+ void find_valid_pharaoh_moves(int i, Move *valid_moves, int *vi)
371
+ {
372
+ for (int j = 0; j < 8; j++)
373
+ {
374
+ int dest = i + directions[j];
375
+ if (!is_piece(board[dest]) && can_move[whose_turn][dest])
376
+ {
377
+ valid_moves[(*vi)++] = new_move(i, dest, 0);
378
+ }
379
+ }
380
+ }
381
+
382
+ void find_valid_sphinx_moves(int i, Move *valid_moves, int *vi)
383
+ {
384
+ enum Player player = get_owner(board[i]);
385
+ enum Orientation orientation = get_orientation(board[i]);
386
+ int rotation = player == Silver ? (orientation == North ? -1 : 1) : (orientation == South ? -1 : 1);
387
+ valid_moves[(*vi)++] = new_move(i, i, rotation);
388
+ }
389
+
390
+ void setup_board(char *init_board[120])
391
+ {
392
+ uint64_t hash = 0;
393
+ for (int i = 0; i < 120; i++)
394
+ {
395
+ board[i] = str_to_square(init_board[i]);
396
+ if (board[i] > 0)
397
+ hash ^= keys[board[i]][i];
398
+ }
399
+ hashes[0] = hash;
400
+
401
+ for (int i = 0; i < TABLE_SIZE; i++)
402
+ {
403
+ table[i].key = 0;
404
+ table[i].depth = 0;
405
+ table[i].flag = 0;
406
+ table[i].score = 0;
407
+ table[i].move = 0;
408
+ }
409
+ }
410
+
411
+ Square str_to_square(char *str)
412
+ {
413
+ enum Player player;
414
+ enum Piece piece;
415
+ enum Orientation orientation;
416
+
417
+ if (str[0] != '-')
418
+ {
419
+ if (islower(str[0]))
420
+ player = Silver;
421
+ else
422
+ player = Red;
423
+
424
+ char p = tolower(str[0]);
425
+ if (p == 'a')
426
+ piece = Anubis;
427
+ else if (p == 'p')
428
+ piece = Pyramid;
429
+ else if (p == 's')
430
+ piece = Scarab;
431
+ else if (p == 'x')
432
+ piece = Pharaoh;
433
+ else
434
+ piece = Sphinx;
435
+
436
+ char o = str[1];
437
+ if (o == '0')
438
+ orientation = North;
439
+ else if (o == '1')
440
+ orientation = East;
441
+ else if (o == '2')
442
+ orientation = South;
443
+ else
444
+ orientation = West;
445
+
446
+ return (Square)((int)player << 1 | (int)piece << 2 | (int)orientation << 5);
447
+ }
448
+
449
+ return (Square)0;
450
+ }
451
+
452
+ void print_board()
453
+ {
454
+ for (int i = 0; i < 120; i++)
455
+ {
456
+ print_piece(board[i]);
457
+ if ((i + 1) % 12 == 0)
458
+ printf("\n");
459
+ }
460
+ }
461
+
462
+ void print_piece(Square s)
463
+ {
464
+ enum Player player = get_owner(s);
465
+ if (is_piece(s))
466
+ {
467
+ enum Piece piece = get_piece(s);
468
+ enum Orientation orientation = get_orientation(s);
469
+ switch (piece)
470
+ {
471
+ case Anubis:
472
+ if (player == Silver)
473
+ printf("a");
474
+ else
475
+ printf("A");
476
+ break;
477
+
478
+ case Pyramid:
479
+ if (player == Silver)
480
+ printf("p");
481
+ else
482
+ printf("P");
483
+ break;
484
+
485
+ case Scarab:
486
+ if (player == Silver)
487
+ printf("s");
488
+ else
489
+ printf("S");
490
+ break;
491
+
492
+ case Pharaoh:
493
+ if (player == Silver)
494
+ printf("x");
495
+ else
496
+ printf("X");
497
+ break;
498
+
499
+ case Sphinx:
500
+ if (player == Silver)
501
+ printf("l");
502
+ else
503
+ printf("L");
504
+ break;
505
+ default:
506
+ printf("-");
507
+ break;
508
+ }
509
+ switch (orientation)
510
+ {
511
+ case North:
512
+ printf("0");
513
+ break;
514
+ case East:
515
+ printf("1");
516
+ break;
517
+ case South:
518
+ printf("2");
519
+ break;
520
+ case West:
521
+ printf("3");
522
+ break;
523
+ default:
524
+ printf("-");
525
+ break;
526
+ }
527
+ }
528
+ else
529
+ printf("--");
530
+ }
531
+
532
+ void reset_undo()
533
+ {
534
+ undo_index = 0;
535
+ for (int i = 0; i < MAX_DEPTH; i++)
536
+ {
537
+ undo_moves[i] = 0;
538
+ undo_capture_indices[i] = 0;
539
+ undo_capture_squares[i] = 0;
540
+ }
541
+ }
542
+
543
+ void init_zobrist()
544
+ {
545
+ for (int i = 0; i < 0xFF; i++)
546
+ {
547
+ for (int j = 0; j < 120; j++)
548
+ {
549
+ keys[i][j] = random_number();
550
+ }
551
+ }
552
+ turn_key = random_number();
553
+ }
554
+
555
+ bool is_move_legal(Move move)
556
+ {
557
+ int start = get_start(move);
558
+ int end = get_end(move);
559
+ if (is_piece(board[start]) && get_owner(board[start]) == whose_turn)
560
+ {
561
+ if (!is_piece(board[end]) || get_rotation(move) != 0)
562
+ return true;
563
+ else if (is_piece(board[end]) && get_piece(board[start]) == Scarab && get_piece(board[end]) < 3)
564
+ return true;
565
+ }
566
+ return false;
567
+ }
568
+
569
+ uint64_t get_board_hash()
570
+ {
571
+ uint64_t hash = 0;
572
+ for (int i = 0; i < 120; i++)
573
+ {
574
+ if (board[i] > 0)
575
+ hash ^= keys[board[i]][i];
576
+ }
577
+ return hash;
578
+ }
@@ -0,0 +1,197 @@
1
+ #ifndef KHET_LIB_H_INCLUDED
2
+ #define KHET_LIB_H_INCLUDED
3
+
4
+ #include <stdint.h>
5
+ #include <stdbool.h>
6
+
7
+ typedef uint8_t Square;
8
+ typedef uint32_t Move;
9
+
10
+ #define NUM_VALID_MOVES 125
11
+ #define MAX_SCORE 9999999
12
+ #define MAX_DEPTH 25
13
+
14
+ enum Player
15
+ {
16
+ Silver,
17
+ Red
18
+ };
19
+ enum Piece
20
+ {
21
+ Anubis = 1,
22
+ Pyramid = 2,
23
+ Scarab = 3,
24
+ Pharaoh = 4,
25
+ Sphinx = 5
26
+ };
27
+ enum Orientation
28
+ {
29
+ North,
30
+ East,
31
+ South,
32
+ West
33
+ };
34
+
35
+ extern enum Player whose_turn;
36
+
37
+ // north, east, south, west, diagonals
38
+ static const int directions[8] = {-12, 1, 12, -1, (12 + 1), (12 - 1), (-12 + 1), (-12 - 1)};
39
+ static const int rotations[2] = {1, -1};
40
+ static const int sphinx_loc[2] = {106, 13};
41
+
42
+ extern Square board[120];
43
+ extern Move undo_moves[MAX_DEPTH];
44
+ extern int undo_capture_indices[MAX_DEPTH];
45
+ extern Square undo_capture_squares[MAX_DEPTH];
46
+ extern int undo_index;
47
+
48
+ static const int can_move[2][120] = {
49
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
50
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,
51
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
52
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
53
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
54
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
55
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
56
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
57
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,
58
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
59
+
60
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
61
+ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
62
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
63
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
64
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
65
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
66
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
67
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
68
+ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
69
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
70
+
71
+ static const int on_board[120] = {
72
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
73
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
74
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
75
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
76
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
77
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
78
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
79
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
80
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
81
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
82
+
83
+ extern void setup_board(char *init_board[120]);
84
+ extern Square str_to_square(char *str);
85
+ extern void print_board();
86
+ extern void print_piece(Square s);
87
+ extern void reset_undo();
88
+
89
+ extern void find_valid_moves(Move *valid_moves, int *vi);
90
+ extern void find_valid_anubis_pyramid_moves(int i, Move *valid_moves, int *vi);
91
+ extern void find_valid_scarab_moves(int i, Move *valid_moves, int *vi);
92
+ extern void find_valid_pharaoh_moves(int i, Move *valid_moves, int *vi);
93
+ extern void find_valid_sphinx_moves(int i, Move *valid_moves, int *vi);
94
+
95
+ extern Move alphabeta_root(int depth, enum Player player);
96
+ extern int alphabeta(int depth, enum Player player, int alpha, int beta);
97
+ extern int calculate_score();
98
+
99
+ extern void make_move(Move move);
100
+ extern void undo_move();
101
+ extern void fire_laser(uint64_t *hash);
102
+ extern bool is_move_legal(Move move);
103
+
104
+ static inline bool is_piece(Square s) { return s > 0; }
105
+
106
+ static inline enum Player get_owner(Square s) { return (enum Player)(s >> 1 & 0x1); }
107
+ static inline enum Piece get_piece(Square s) { return (enum Piece)(s >> 2 & 0x7); }
108
+ static inline enum Orientation get_orientation(Square s) { return (enum Orientation)(s >> 5 & 0x7); }
109
+
110
+ static inline Move new_move(int start, int end, int rotation) { return start << 1 | end << 8 | (rotation + 2) << 15; }
111
+ static inline int get_start(Move m) { return m >> 1 & 0x7F; }
112
+ static inline int get_end(Move m) { return m >> 8 & 0x7F; }
113
+ static inline int get_rotation(Move m) { return (m >> 15 & 0x3) - 2; }
114
+
115
+ static inline Square rotate(Square s, int rotation)
116
+ {
117
+ int orientation = get_orientation(s);
118
+ orientation = (orientation + rotation) % 4;
119
+ if (orientation < 0)
120
+ orientation += 4;
121
+ return (s & 0x1F) + (orientation << 5);
122
+ }
123
+
124
+ static inline enum Player opposite_player(enum Player player)
125
+ {
126
+ return player == Red ? Silver : Red;
127
+ }
128
+
129
+ #define Dead -1
130
+ #define Absorbed -2
131
+
132
+ // [laser direciton][piece type][piece orientation] = reflection result
133
+ // anubis, pyramid, scarab, pharaoh, sphinx
134
+ static const int reflections[4][5][4] = {
135
+ {// North
136
+ {Dead, Dead, Absorbed, Dead},
137
+ {Dead, East, West, Dead},
138
+ {West, East, West, East},
139
+ {Dead, Dead, Dead, Dead},
140
+ {Absorbed, Absorbed, Absorbed, Absorbed}},
141
+ {// East
142
+ {Dead, Dead, Dead, Absorbed},
143
+ {Dead, Dead, South, North},
144
+ {South, North, South, North},
145
+ {Dead, Dead, Dead, Dead},
146
+ {Absorbed, Absorbed, Absorbed, Absorbed}},
147
+ {// South
148
+ {Absorbed, Dead, Dead, Dead},
149
+ {East, Dead, Dead, West},
150
+ {East, West, East, West},
151
+ {Dead, Dead, Dead, Dead},
152
+ {Absorbed, Absorbed, Absorbed, Absorbed}},
153
+ {// West
154
+ {Dead, Absorbed, Dead, Dead},
155
+ {North, South, Dead, Dead},
156
+ {North, South, North, South},
157
+ {Dead, Dead, Dead, Dead},
158
+ {Absorbed, Absorbed, Absorbed, Absorbed}}};
159
+
160
+ extern time_t start_time;
161
+ extern int max_time;
162
+ extern uint64_t keys[0xFF][120];
163
+ extern uint64_t hashes[MAX_DEPTH];
164
+ extern uint64_t turn_key;
165
+ extern int move_num;
166
+ extern bool checkmate;
167
+
168
+ extern void init_zobrist();
169
+ static uint64_t seed = 1070372;
170
+ static inline uint64_t random_number()
171
+ {
172
+ seed ^= seed >> 12;
173
+ seed ^= seed << 25;
174
+ seed ^= seed >> 27;
175
+ return seed * 0x2545F4914F6CDD1DLL;
176
+ }
177
+
178
+ #define TABLE_SIZE 0x400000
179
+
180
+ #define EXACT 0
181
+ #define ALPHA 1
182
+ #define BETA 2
183
+ typedef struct HashEntry
184
+ {
185
+ uint64_t key;
186
+ int depth;
187
+ int flag;
188
+ int score;
189
+ Move move;
190
+ } HashEntry;
191
+
192
+ extern HashEntry table[TABLE_SIZE];
193
+ static inline HashEntry *search_table(uint64_t key) { return &table[key % TABLE_SIZE]; };
194
+ extern void insert_table(uint64_t key, int depth, int flag, int score, Move move);
195
+ extern uint64_t get_board_hash();
196
+
197
+ #endif // KHET_LIB_H_INCLUDED extern uint64_t get_board_hash();
data/khetai.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/khetai/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "khetai"
5
+ spec.version = KhetAI::VERSION
6
+ spec.authors = ["jkugs"]
7
+ spec.email = ["jkuglics@gmail.com"]
8
+
9
+ spec.summary = "Khet AI"
10
+ spec.homepage = "https://github.com/jkugs/khetai"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = spec.homepage
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ spec.extensions = %w[ext/khetai/extconf.rb]
26
+ end
@@ -0,0 +1,3 @@
1
+ module KhetAI
2
+ VERSION = "0.1.6"
3
+ end
data/lib/khetai.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "khetai/version"
2
+ require "khetai/khetai"
3
+
4
+ module KhetAI
5
+ class Error < StandardError; end
6
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: khetai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - jkugs
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - jkuglics@gmail.com
16
+ executables: []
17
+ extensions:
18
+ - ext/khetai/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".gitignore"
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE.txt
25
+ - README.md
26
+ - README_ORIGINAL.md
27
+ - Rakefile
28
+ - bin/console
29
+ - bin/setup
30
+ - ext/khetai/extconf.rb
31
+ - ext/khetai/khetai.c
32
+ - ext/khetai/khetai_lib.c
33
+ - ext/khetai/khetai_lib.h
34
+ - khetai.gemspec
35
+ - lib/khetai.rb
36
+ - lib/khetai/version.rb
37
+ homepage: https://github.com/jkugs/khetai
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ homepage_uri: https://github.com/jkugs/khetai
42
+ source_code_uri: https://github.com/jkugs/khetai
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 2.3.0
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.1.2
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Khet AI
62
+ test_files: []