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 +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: []
|