600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > java通用象棋游戏_在通用国际象棋界面周围模拟GraphQL包装器

java通用象棋游戏_在通用国际象棋界面周围模拟GraphQL包装器

时间:2021-03-19 20:47:58

相关推荐

java通用象棋游戏_在通用国际象棋界面周围模拟GraphQL包装器

java通用象棋游戏

The Universal Chess Interface (UCI) has been around a long time and used by many chess engines. What does GraphQL bring to the mix?

通用国际象棋界面(UCI)已经存在了很长时间,并且被许多国际象棋引擎使用。 GraphQL带来了什么?

I had some email exchanges with an owner of a chess website recently, and he asked me what I knew about UCI and websockets. That got me looking closer at UCI and thinking about how and why one would wrap a GraphQL schema around it.

最近,我与一个国际象棋网站的所有者进行了一些电子邮件往来,他问我对UCI和Websockets的了解。 这使我更加了解UCI,并思考了如何以及为什么将GraphQL模式包装在它周围。

通用国际象棋界面 (The Universal Chess Interface)

The UCI has been around for over a decade, and is based on standard I/O messaging between a chess engine and its client (usually a graphical UI). The client submits a message to the chess engine, and the enginemaysend back a response. I saymaybecause UCI doesn’t require a response for some incoming messages to the chess engine.

UCI已经存在了十多年 ,它基于国际象棋引擎与其客户端(通常是图形UI)之间的标准I / O消息传递。 客户向国际象棋引擎提交一条消息,该引擎可能发回回应。 我说的可能是因为UCI不需要某些传入邮件的国际象棋引擎的响应。

The engine alsomightsend back more than one response. During game analysis, the engine will be sending backinfopackets detailing it’s thinking. The client says what the start position is, tells it “Go”, and the engine keeps going until it arrives at abest move.During the process, the engine streams back messages about what it’s thinking.

引擎也可能发送回多个响应。 在游戏分析过程中,引擎将发回详细说明其思想的信息包。 客户说出起始位置是什么,告诉它“开始”,发动机一直运转直到到达最佳移动位置。在此过程中,引擎会流回有关其想法的消息。

The UCI specification is short, and you don’t need a deep understanding of it to see the basics of how it works — and it has worked well so far, so why monkey with it?

UCI规范很短,您不需要深入了解它即可了解其工作原理,而且到目前为止效果很好,那么为什么要继续使用它呢?

If the engine resides on a remote server, then just open a websocket and do as you would normally do. That works, of course, but it doesn’t hurt to look at the pros and cons of doing things slightly differently.

如果引擎位于远程服务器上,则只需打开一个websocket并按照通常的方式进行即可。 当然,这是可行的,但是以稍微不同的方式看待事情的利弊也不会受到损害。

国际象棋引擎与国际象棋服务器 (Chess Engine versus Chess Server)

I want to start by mocking up a UCI service. A next step will be to build a functional prototype and, as always seems to be the case, its not hard to find a Node.js package that helps with most of the work.

我想从模拟UCI服务开始。 下一步将是构建功能性的原型,并且似乎总是如此,找到有助于大多数工作的Node.js包并不难。

One of the more popular engines is called Stockfish. Someone has taken the trouble of transpiling it from C++ to JavaScript so that the engine can be run wholly in Node.js.

一种最受欢迎​​的引擎称为Stockfish 。 有人将其从C ++转换为JavaScript的麻烦使引擎可以完全在Node.js中运行。

It’s this simple:

就这么简单:

create and cd into a folder创建并CD进入文件夹

npm init

npm init

npm install stockfish

npm install stockfish

node node_modules/stockfish/src/stockfish.js

node node_modules/stockfish/src/stockfish.js

And now you are in a Stockfish command shell, and can start typing in commands.

现在,您处于Stockfish命令外壳中,并且可以开始输入命令。

The first thing to do is to fire up the UCI interface by typinguci. You should get a response like this:

首先要做的是通过键入uci来启动UCI接口。 您应该得到这样的响应:

id name Stockfish.js 8id author T. Romstad, M. Costalba, J. Kiiski, G. Linscottoption name Contempt type spin default 0 min -100 max 100option name Threads type spin default 1 min 1 max 1option name Hash type spin default 16 min 1 max 2048option name Clear Hash type buttonoption name Ponder type check default falseoption name MultiPV type spin default 1 min 1 max 500option name Skill Level type spin default 20 min 0 max 20option name Move Overhead type spin default 30 min 0 max 5000option name Minimum Thinking Time type spin default 20 min 0 max 5000option name Slow Mover type spin default 89 min 10 max 1000option name nodestime type spin default 0 min 0 max 10000option name UCI_Chess960 type check default falseoption name UCI_Variant type combo default chess var chess var giveaway var atomic var crazyhouse var horde var kingofthehill var racingkings var relay var threecheckoption name Skill Level Maximum Error type spin default 200 min 0 max 5000option name Skill Level Probability type spin default 128 min 1 max 1000uciok

It shows what the option settings are set to and then returnsuciok, meaning the interface is ready. The next step is to set options and then callisready, and when the engine respondsreadyok, it can start analyzing a chess position.

它显示了选项设置所设置的内容,然后返回uciok,这意味着界面已准备就绪。 下一步是设置选项,然后调用isready,当引擎响应readyok,它可以开始分析棋的位置。

I won’t actually be using this engine for my mock implementation, but it does come in handy if I want to examine what a command does using a real engine.

我实际上不会在模拟实现中使用此引擎,但是如果我想检查使用真实引擎的命令的功能,它确实会派上用场。

In a real server implementation, I would be firing up one engine per client (or perhaps more). GraphQL helps me define an API that would support multiple clients running multiple engines.

在真实的服务器实现中,我将为每个客户端启动一个引擎(或可能更多)。 GraphQL帮助我定义了一个API,它将支持运行多个引擎的多个客户端。

GraphQL (GraphQL)

For this mock, I’ve divide up the UCI component into HTTP requests of a call/response nature, and websocket subscriptions for handling streaming responses. This means that a socket is only open if the user wants to subscribe to detailed information about what the engine is thinking. Furthermore, I can refine the number and types ofinfomessages I receive on the client, so that socket traffic is minimized.

对于此模拟,我将UCI组件分为具有呼叫/响应性质的HTTP请求和用于处理流响应的websocket订阅。 这意味着只有在用户希望订阅有关引擎在想什么的详细信息时,套接字才会打开。 此外,我可以优化在客户端上收到的信息消息的数量和类型,以使套接字流量最小化。

每个命令都会得到一个响应(Each command gets a response)

Because client-server interaction is happening (in most cases) over unreliable HTTP, it’s important that the client (running on the browser) knows that its message got through to the server. The UCI commandsetoption, for instance, doesn’t send a response back according to the specification.

由于客户端-服务器交互(大多数情况下)是通过不可靠的HTTP发生的,因此(在浏览器上运行的)客户端知道其消息已到达服务器很重要。 例如,UCI命令setoption不会根据规范发送回响应。

That’s fine for an interface based on reliable sockets, not so good for HTTP requests. GraphQL ensures that there is a response sent back to every received request, if only to acknowledge that the request was received.

对于基于可靠套接字的接口而言,这很好,而对于HTTP请求而言则不太好。 GraphQL确保仅在确认已接收到请求的情况下,才将响应发送回每个收到的请求。

每个命令及其参数都是类型安全的(Each command and its arguments are type-safe)

GraphQL interfaces are schema-based, UCI interfaces are not (they’re based on descriptive text in the specification). If a client to sends an invalid command, the chess engine server should never have to deal with it. By defining UCI in terms of types in GraphQL, I can waylay an errant command at the API level — in GraphQL — before it gets to the engine.

GraphQL接口是基于架构的,而UCI接口不是(它们基于规范中的描述性文本)。 如果客户端发送无效命令,则象棋引擎服务器永远不必处理该命令。 通过根据GraphQL中的类型定义UCI,我可以在GraphQL中在API级别处放置错误命令,然后再到达引擎。

GraphQL解析器可以将响应分解为JSON结构(GraphQL resolvers can decompose responses into JSON structures)

JavaScript is the language of the internet, and GraphQL returns JSON responses. By having the GraphQL resolvers take a UCI response and break it down in a fine-grained and structured way, the client is alleviated of the UCI response parsing task.

JavaScript是互联网的语言,而GraphQL返回JSON响应。 通过让GraphQL解析器获取UCI响应并以细粒度和结构化的方式将其分解,可以减轻客户端的UCI响应解析任务。

我可以使用Apollo GraphQL工具轻松模拟我的API(I can easily mock my API using Apollo GraphQL Tools)

After designing an API, but before heading off to implementation land, it’s useful to first check the API look and feel of using mocks. The graphql-tools package makes this easy and painless. You can even mix mocks with real resolvers, giving you the option of iterative implementation of your API.

在设计了API之后,但在进行实施之前,先检查一下使用模拟的API外观是很有用的。 graphql-tools软件包使此操作变得简单而轻松 。 您甚至可以将模拟与真实的解析器混合使用,从而可以选择迭代实现API。

我可以通过GraphiQL服务与API进行交互 (I can interact with the API through the GraphiQL service)

GraphiQL is the interactive service that can be run atop a GraphQL server. This is convenient for doing ad hoc testing of the API, based on either a mock or implementation.

GraphiQL是可以在GraphQL服务器之上运行的交互式服务。 这对于基于模拟或实现对API进行临时测试非常方便。

继续模拟! (On to the Mocking!)

Let’s take a look at the dependencies first:

让我们先来看一下依赖关系:

"dependencies": {"apollo-server-express": "^1.3.2","babel-cli": "^6.26.0","babel-preset-env": "^1.6.1","express": "^4.16.2","graphql": "^0.12.3","graphql-subscriptions": "^0.5.7","graphql-tag": "^2.7.3","graphql-tools": "^2.21.0","stockfish": "^8.0.0","subscriptions-transport-ws": "^0.9.5"},"devDependencies": {"casual": "^1.5.19","randexp": "^0.4.8"},

I’m calling this serverchessQ, and the server itself will be based onapollo-server-express, the Apollo Group’s GraphQL server implementation. Thestockfish.jspackage, mentioned earlier, is included as an embedded engine. Though this mock doesn’t use it, it’s there for reference. In a real implementation, one would probably access an externally running engine.

我将此服务器称为ChessQ,服务器本身将基于Apollo Group的GraphQL服务器实现apollo-server-express。 前面提到的stockfish.js软件包是作为嵌入式引擎提供的。 尽管此模拟程序未使用它,但仍可供参考。 在实际的实现中,可能会访问外部运行的引擎 。

Included iscasualandrandexpfor helping with the mocks. Finally,graphql-subscriptionsandsubscriptions-transport-wswill handle the streaming messages coming back from our mock while it is pretending to analyze.

其中包括casualrandexp,可帮助您进行randexp。 最后,graphql-subscriptionssubscriptions-transport-ws将在假装分析时处理从我们的模拟返回的流消息。

ChessQ模式 (The chessQ schema)

Let me first say that I haven’t spent time polishing up the schema, so consider it a first draft. It’s functional, but it will probably change as I continue to develop it. At the end of this article, I’ll link to a stable branch that corresponds to what is described here. I won’t be going into painstaking detail on the code, but will link to relevant source in GitHub where appropriate. Watch for those.

首先让我说我没有花时间完善模式,因此将其视为初稿。 它具有功能,但是随着我继续开发它可能会改变。 在本文的结尾,我将链接到一个稳定的分支,该分支与此处描述的相对应。 我不会详细介绍代码,但会在适当的地方链接到GitHub中的相关源代码。 注意那些。

First thing is to define the top-level queries. These are the entry points for the client:

首先是定义顶级查询 。 这些是客户端的入口点:

type Query {createEngine: EngineResponseuci(engineId: String!): UciResponse!register(engineId: String!, name: String, code: String): StringregisterLater(engineId: String!): StringsetSpinOption(engineId: String!, name: String!, value: Int!): String!setButtonOption(engineId: String!, name: String!): String!setCheckOption(engineId: String!, name: String!, value: Boolean!): String!setComboOption(engineId: String!, name: String!, value: String!): String!quit(engineId: String!): String!isready(engineId: String!): String!}

ThecreateEnginerequest will return an EngineResponse, inside of which is an engine instance identifier that is used for subsequent requests:

createEngine请求将返回EngineResponse ,其中包含用于后续请求的引擎实例标识符:

{"data": {"createEngine": {"engineId": "46d89031-03c3-4851-ae97-34e4b5d1d7c6"}}}

Theucirequest will return a UciResponse detailing the current option settings. In the GraphQL schema, each type of option (spin, check, button, and combo) has its own specific fields:

uci请求将返回详细说明当前选项设置的UciResponse 。 在GraphQL模式中,每种类型的选项(旋转,选中,按钮和组合)都有其自己的特定字段:

interface Option {name: String!type: String!}type SpinOption implements Option {name: String!type: String!value: Int!min: Int!max: Int!}type ButtonOption implements Option {name: String!type: String!}type CheckOption implements Option {name: String!type: String!value: Boolean!}type ComboOption implements Option {name: String!type: String!value: String!options: [String!]!}

A mockuci querymight be:

模拟的uci query可能是:

query uci {uci(engineId: "46d89031-03c3-4851-ae97-34e4b5d1d7c6") {uciokayoptions {nametype... on SpinOption {valueminmax}}}}

and the response:

和响应:

{"data": {"uci": {"uciokay": true,"options": [{"name": "Porro tempora minus","type": "check"},{"name": "Id ducimus","type": "combo"},{"name": "Aliquam voluptates","type": "button"},{"name": "Voluptatibus illo ullam","type": "spin","value": 109,"min": 0,"max": 126},{"name": "Temporibus et nisi","type": "check"}]}}}

Technically, some of these commands could be thought of as Mutations, not Queries, since they change the state of the engine. But Mutations in GraphQL are primarily about sequential order-of-execution and that does not apply in this case: any option can be set in any order.

从技术上讲,由于其中某些命令会更改引擎的状态,因此可以将它们视为“变异”而不是“查询”。 但是GraphQL中的突变主要与顺序执行顺序有关,在这种情况下不适用:可以按任何顺序设置任何选项。

Ultimately, each engine instance will need to maintain some indication of its state (not implemented in this mock). These might be:

最终,每个引擎实例将需要维护其状态的某种指示(此模拟中未实现)。 这些可能是:

enum eEngineState {CREATEDINITIALIZEDREADYRUNNINGSTOPPED}

If for instance, agocommand is sent before the engine state isREADY, then that would be an error.

例如,如果go在引擎状态为READY之前发送命令,这将是一个错误。

就绪架构 (The Ready Schema)

When the engine is READY, three new commands are possible:

当引擎为READY时,可以使用三个新命令:

ucinewgame: tell the engine a new game has started

ucinewgame:告诉引擎新游戏已经开始

position: tell the engine what the starting position is (along with any moves from that position)

position:告诉引擎起始位置是什么(以及从该位置开始的任何移动)

go: start the engine!

go:启动引擎!

Before issuing thegocommand, the client has the option to subscribe to anyinfomessages streaming in through the websocket (otherwise, there will be just aBestMoveHTTP response when the engine is finished).

在发出go命令之前,客户端可以选择通过websocket订阅流式传输的任何信息消息 (否则,只有BestMove引擎完成时的HTTP响应)。

Details on how to set up a subscription service using graphql-subscriptions can be found elsewhere, so here I will focus on the schema and resolver implementation.

有关如何使用graphql-subscriptions设置订阅服务的详细信息,可以在其他地方找到,因此这里我将重点介绍模式和解析程序的实现。

The schema defines the types ofSubscriptionsavailable. For this mock, there is just one:

该模式定义了可用的Subscriptions类型。 对于此模拟,只有一个:

type Subscription {info: Info}

TheInfotype, like theOptiontype, is a union of several specific info structures:

Info类型与Option类型一样,是几个特定信息结构的联合:

type Score {cp: Int!depth: Int!nodes: Int!time: Int!pv: [Move!]!}type Depth {depth: Int!seldepth: Int!nodes: Int}type Nps {value: Int!}type BestMove {value: Move!,ponder: Move}union Info = Score | Depth | Nps | BestMove

The precise meaning of theseInfomessages is irrelevant to this discussion. The important thing is to know that they come in any order, except for theBestMovemessage, which is last.

这些Info消息的确切含义与此讨论无关。 重要的是要知道它们以任何顺序出现,除了最后移动的BestMove消息。

The client subscribes to info messages using asubscriptionrequest like the following:

客户端使用如下subscription请求来subscription信息消息:

subscription sub {info {... on Score {pv}... on BestMove {value}}}

There’s a resolver to handle theSubscriptionrequest, which uses methods in thegraphql-subscriptionspackage:

有一个解析器可以处理Subscription请求,它使用graphql-subscriptions包中的方法:

import {PubSub, withFilter} from 'graphql-subscriptions';...resolvers: ...Subscription: {info: {subscribe: withFilter(() => pubsub.asyncIterator(TOPIC), (payload, variables) => {return true})}}...

In this subscription resolver, the the function passed towithFilterpasses every message back. But a real subscribe resolver could be more discriminating based on parameters passed in by the client.

在此订阅解析器中,传递给withFilter的函数将每条消息传回。 但是,真正的订阅解析器可能会根据客户端传递的参数来进行区分。

看到它在行动 (Seeing it in action)

You can query, mutate, and subscribe in GraphiQL, so there’s no need to write a client for testing purposes. The one gotcha is that GraphiQL will enter “subscription” mode once a subscription is requested, and won’t happily respond to further commands.

您可以在GraphiQL中进行查询,变异和订阅,因此无需编写用于测试目的的客户端。 一个陷阱是,一旦请求订阅,GraphiQL将进入“订阅”模式,并且不会高兴地响应其他命令。

The solution is to have two GraphiQL tabs open in your browser, one for issuing queries and mutations, and the other for listening to subscribed messages.

解决方案是在浏览器中打开两个GraphiQL选项卡,一个选项卡用于发出查询和变异,另一个选项卡用于侦听订阅的消息。

Download the chessQ package, runnpm installand thennpm run dev. The chessQ mock application should now be running.

下载chessQ软件包,运行npm install,然后npm run dev。 现在,chessQ模拟应用程序应该正在运行。

Open two tabs to http://localhost:3001/graphiql.

打开两个选项卡以http:// localhost:3001 / graphiql 。

In one tab, enter:

在一个标签中,输入:

subscription sub {info {__typename... on Score {pv}... on BestMove {value}}}

You’ll see a message that says:

您会看到一条消息,内容为:

"Your subscription data will appear here after server publication!"

To generate messages, there is agoresolver (shown below) that iterates through a static set of info messages and publishes them one by one. Because the subscriber panewill only show one messageat a time, there’s a simplesleepimplementation that slows down the messaging so that you can see them fly by:

要生成消息,go解析器(如下所示)迭代一组静态的信息消息并逐一发布。 由于订户窗格一次只会显示一条消息,因此有一个简单的sleep实现可以减慢消息传递的速度,以便您可以看到它们飞过:

function sleep(ms) {return new Promise(resolve => {setTimeout(resolve, ms)})}...resolvers: {...Mutation: {go: async () => {let info;for (info of InfoGenerator()) {pubsub.publish(TOPIC, {info})await sleep(1000)}return info;}},...

Finally, in the non-subscription tab, start the analysis withgo:

最后,在“非订阅”标签中,使用go开始分析:

mutation go {go {__typenamevalue}}

While this tab is awaiting thegoresponse showing theBestMove, thesubscriptiontab will be catching info messages and displaying them one-by-one.

虽然这片正在等待go展示响应BestMovesubscription选项卡将被捕获信息,然后显示他们一个接一个。

进一步的想法 (Further Thoughts)

Before rolling forth from mock to implementation, a couple of notes:

从模拟过渡到实现之前,请注意以下几点:

The simple pub/sub mechanism used in this example is neither robust or scalable. That’s okay, because there are Redis and RabbitMQ implementations of graphql-subscription that are. A more refined subscription specification could also be defined and give fine-grained control to the subscriber as to which messages are received.

本示例中使用的简单发布/订阅机制既不健壮也不具有可伸缩性。 没关系,因为这里有graphql-subscription的Redis和RabbitMQ实现。 还可以定义更精细的订阅规范,并向订户提供有关接收到哪些消息的细粒度控制。

Not a lot of thought was given to managing websocket lifetime in this mock, which is something that needs to be considered if serving a large number of users.

在此模拟中,对于管理websocket的生存期没有太多考虑,如果服务于大量用户,则需要考虑这一点。

All source code for this article can be found here.

本文的所有源代码都可以在这里找到。

翻译自: /news/mocking-a-graphql-wrapper-around-the-universal-chess-interface-1c5bb1acd821/

java通用象棋游戏

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。