khetai 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +47 -0
- data/README_ORIGINAL.md +40 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/khetai/extconf.rb +2 -0
- data/ext/khetai/khetai.c +51 -0
- data/ext/khetai/khetai_lib.c +578 -0
- data/ext/khetai/khetai_lib.h +197 -0
- data/khetai.gemspec +26 -0
- data/lib/khetai/version.rb +3 -0
- data/lib/khetai.rb +6 -0
- metadata +62 -0
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
data/Gemfile
ADDED
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
|
+
```
|
data/README_ORIGINAL.md
ADDED
@@ -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
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
data/ext/khetai/khetai.c
ADDED
@@ -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
|
data/lib/khetai.rb
ADDED
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: []
|