khetai 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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: []