UPK6W3YC62YCWHXNGGK4PDJBPK5JEZW5R3HYGWDJSVF5ULKFQERAC
LMHTQTH7J7K6ZBBZOOAMMYFCVPCWGUUKDQOTP27NIV7Y7H3ZXPXQC
RKTDDNSGNOB7K27XI6ZBRXHCBOAMAKM667BUQIQSVZ2YJED2YCPQC
BFHHGVOJAAJBFVROYZ42XNVOFOWK7KQKWTYZLF3P5HGA7BHLJXNQC
6XO5SUSOGLDRQAKXHBFQTQPOQ7WMVXIVFVDABQFOQTHKBBTCKD6AC
RDDAB3MJC22EXAXNO4P537HCJGXMXJCRQH75VCTT4VUM2QRRQBOQC
U3JHTSEMJLXNKOMAQJQPZKRW6GRPHYLG6DK53XVADIETKVCS2MFQC
DYHBLNX63S326PH2KNHIBBQ27L2LXRHUVU3SNL3KRITV5RZNWA5AC
5AAIYEWRNV2H5226AFCA3SNM44P6ENVRDXGOYENPSCJV6L2N62AAC
dependencies:
uuid: ^3.0.7
import 'package:dartmcts/net.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'dart:convert';
import 'package:uuid/uuid.dart';
Function? gameHandler;
void serve(TrainableInterface Function() trainer) async {
var handler =
const Pipeline().addMiddleware(logRequests()).addHandler(_handleRequest);
gameHandler = () => trainer.call();
var server = await shelf_io.serve(handler, 'localhost', 5000);
print('Serving at http://${server.address.host}:${server.port}');
}
Map<String, TrainableInterface> gamesInProgress = {};
Future<Response> _handleRequest(Request request) async {
if (request.url.path == "newgame") {
TrainableInterface game = gameHandler?.call();
var uuid = const Uuid();
String id = uuid.v4().toString();
gamesInProgress[id] = game;
return Response.ok(
json.encode({
"id": id,
"player_count": game.playerCount,
"action_space_size": game.actionSpaceSize,
"observation_space_size": game.observationSpaceSize,
"current_player": game.currentPlayer,
"observation": game.observation(),
"legal_actions": game.legalActions()
}),
headers: {'Content-Type': 'application/json'});
} else {
var pieces = request.url.path.split('/');
assert(pieces[0] == "step");
var id = pieces[1];
var action = json.decode(await request.readAsString())['action'];
TrainableInterface? game = gamesInProgress[id]!;
var gameResponse = game.step(action);
var stepResponse = json.encode({
"observation": game.observation(),
"legal_actions": game.legalActions(),
"next_player": game.currentPlayer,
"reward": gameResponse.reward,
"done": gameResponse.done
});
if (gameResponse.done) {
// free memory for finished games
gamesInProgress.remove(id);
game = null;
}
return Response.ok(stepResponse,
headers: {'Content-Type': 'application/json'});
}
}
return l;
}
List<double> encodeGame(TicTacToeGame game) {
List<double> l = [];
List<double> myLocations = List.filled(9, 0);
List<double> opponentLocations = List.filled(9, 0);
game.board.asMap().forEach((i, player) {
if (player == null) return;
if (player == game.currentPlayer) {
myLocations[i] = 1;
} else {
opponentLocations[i] = 1;
}
});
l.addAll(myLocations);
l.addAll(opponentLocations);
// legalMoves must always be appended to the observation
l.addAll(legalMoves(game));
return l;
class TicTacToeNNInterface extends TrainableInterface {
TicTacToeGame game = TicTacToeGame.newGame() as TicTacToeGame;
@override
int get playerCount => 2;
@override
int get currentPlayer => game.currentPlayer == TicTacToePlayer.O ? 0 : 1;
@override
List<double> legalActions() {
return legalMoves(game);
}
@override
List<double> observation() {
return encodeGame(game);
}
@override
StepResponse step(int move) {
bool done = false;
List<double> reward = List.filled(playerCount, 0.0);
game = game.cloneAndApplyMove(move, null);
if (game.getMoves().length == 0 || game.winner != null) {
done = true;
if (game.winner == null) {
// tie
reward = [0, 0];
} else {
// clear winner - the winner gets 1.0 - everyone else gets -1.0 reward
reward = [-1.0, -1.0];
reward[game.winner! == TicTacToePlayer.O ? 0 : 1] = 1.0;
}
}
return StepResponse(
done: done,
reward: reward,
);
}
}
/// Initialize and optionally set a value in a one-hot array
List<double> initOneHot(int length, {double filler = 0, int? value}) {
var l = List<double>.filled(length, filler);
if (value != null) {
l[value] = 1;
}
return l;
}
/// A tuple for move and score
class MoveScore<Move> {
Move move;
double score;
MoveScore(this.move, this.score);
@override
String toString() {
return 'MoveScore(score: $score, move: $move)';
}
}
/// A response for a move
class StepResponse {
bool done = false;
List<double> reward = [];
StepResponse({required this.done, required this.reward});
}
abstract class TrainableInterface {
int get actionSpaceSize {
return legalActions().length;
}
int get observationSpaceSize {
return observation().length - legalActions().length;
}
late int playerCount;
late int currentPlayer;
List<double> observation();
List<double> legalActions();
StepResponse step(int move);
}
import 'package:dartmcts/trainingserver.dart';
import 'package:dartmcts/tictactoe.dart';
void main() {
serve(() => TicTacToeNNInterface());
}