当前位置:网站首页>Architectural concept exploration: Taking the development of card games as an example
Architectural concept exploration: Taking the development of card games as an example
2022-06-11 14:37:00 【Deep learning and python】
author | Enrico Piccinin
translator | Knowingly mountain
planning | Ding Xiaoyun
COVID-19 made me miss meeting my friends 、 Opportunities to discuss and play card games .
Zoom It can solve some urgent problems , But how to play card games ? How to play our Scopone Well ?
therefore , I decided to develop one that I could play with my friends Scopone game , At the same time, test some architecture concepts in the code that I have been fascinated with for a long time .
All the source code of the game can be found in this code base .
1 What answers do I want
Free deployment server
An interactive card game supporting multiple players is composed of client and server . The server is deployed in the cloud , But where on the end ? As a component running on a dedicated server ? Or as Kubernetes Hosting... In a cluster Docker Mirror image ? Or as a serverless function ?
I don't know which is the best choice , But what I care about is whether the maintenance of the core logic of the game can be independent of the deployment model .
Independent of UI Framework or library
“Angular It's the best ”.“ No ,React Better and faster .” Such arguments are everywhere . But does it really matter ? Shouldn't we treat most of the front-end logic as pure Javascript or Typescript Code , Completely independent of UI Frame or library ? I think it's OK , But I still want to have a real try .
Automatically test the possibility of multi-user interaction scenarios
Card games are like other interactive applications today , There are multiple users interacting in real time through the central server . for example , When a player plays a card , Everyone else needs to see this card in real time . In limine , I don't know how to test such applications . Is it possible to use simple JavaScript Test library ( Such as Mocha) And standard test practices automatically test it ?
Scopone The game can answer my questions
Scopone The game provides me with a good chance , Let me answer my own questions in a specific way . therefore , I decided to try to achieve it , See what I can learn from it .
2 The whole idea
Scopone The rules of the game
Scopone It is a traditional Italian card game ,4 Players are divided into 2 Group , Each group 2 people , Yes 40 card .
At the beginning of the game , Every player gets 10 card , The first player plays the first card , This card is placed face up on the table . Then the second player plays cards . If this card has the same level as the card on the table , The second player is from the table “ take ” This card . If there are no cards on the table , The player who takes the card gets “Scopa” Score of . Then the third player plays cards , And so on , Until all the cards are played .
The rules are over , The key point here is , When players play cards , They change the state of the game , for example “ Which papers are facing up ” or “ Which players can play the next card ”.
Application structure and technology stack
Scopone The game requires one server instance and four client instances , Four players start the client on their device .
If we pay attention to the interaction between various elements in the game , We can know :
- Players perform actions , For example, players play cards ;
- As a result of the player's actions , All players need to update the status of the game .
This means that the client and server need a two-way communication protocol , Because the client must send commands to the server , The server needs to push the updated status to the client .WebSocket Is a protocol suitable for use here , It is supported by various programming languages .
The server side uses Go The realization of language , Because it's good for WebSocket Have good support , Different deployment models are also supported , let me put it another way , It can be deployed as a dedicated server 、Docker Mirror or Lambda.
The client is a browser based application , In two different ways : One is Angular, The other is React. Both versions use TypeScript and RxJs, To achieve responsive design .
The following figure shows the overall architecture of the game .
Orders and events
In short , The process of this game is like this :
- The client sends commands to the server through messages ;
- The server updates the game status ;
- The server pushes the latest status of the game to the client through a message ;
- When the client receives a message from the server , Think of it as an event that triggers a client state update .
This cycle repeats itself , Until the end of the game .
3 Free deployment server side
The server receives the command message sent by the client , And update the status of the game according to these commands , Then send the updated status to the client .
Client pass WebSocket Channel sends command messages , It will be converted to server specific API Call to .
API The call generates a response , It will be converted into a set of messages , The news passed through WebSocket The channel is sent to each client .
therefore , There are two different layers on the server side , They have different responsibilities : Game logic layer and WebSocket Mechanism layer .
Game logic layer
This layer is responsible for implementing game logic , That is, update the game status according to the received command , And return to the latest status , Send to each client .
therefore , This layer can use internal states and a set of... That implement command logic API To achieve .API The latest status will be returned to the client .
WebSocket Mechanism layer
This layer is responsible for transferring data from WebSocket The message received by the channel is converted to the corresponding API call . Besides , It also needs to update the status ( call API Generated response ) Converted to messages pushed to the corresponding client .
Dependencies between layers
Based on the previous discussion , The game logic layer is independent of WebSocket, It's just a group of returning states API.
WebSocket The mechanism layer implements WebSocket characteristic , This layer will depend on the selected deployment model .
for example , If we decide to deploy the server side as a dedicated server , Then you need to choose to implement WebSocket Package of agreement ( Here we choose Gorilla), And if we decide to be AWS Lambda Function to deploy , Then we need to rely on WebSocket Agreed Lambda Realization .
If we want to keep the game logic layer and WebSocket The mechanism layer is strictly separated , Is to import the former from the latter ( One way ), Then the game logic layer will worry about which specific deployment model to choose .
Based on this strategy , We can only develop a single version of the game logic , And freely deploy servers everywhere .
This has several advantages . for example , When developing the client , We can run it locally Gorilla WebSocket Realization , This will be very convenient , Even in VSCode Enable debug mode in . This allows you to set breakpoints in the server code , Debug game logic through various commands sent by the client .
When deploying a game to a server in a production environment ( So I can play real-time games with my friends ), You can directly deploy the same game logic to the cloud , For example, Google application engine (GAE).
Besides , When I found out whether we were playing games or not , Google will charge the lowest fee (GAE Always keep at least one server open ), I can migrate the server to... Without changing the game logic code AWS Lambda Of “ On demand ” Charging model .
4 Independent of UI Framework or library
The big problem now is : choice Angular still React?
I also asked myself another question : Is it possible to use TypeScipt Develop most of the client logic , Independent of the front-end framework or library used to manage views ?
it turns out to be the case that , At least in this case , It is possible , There are just some interesting side effects .
Design of application front end : View layer and service layer
There are three simple ideas for designing the front end of an application :
- The client is divided into two layers :
- The view layer is a composable component (Angular and React You can put UI As a combination of components ), Pure presentation logic can be implemented .
- Service layer , use TypeScript Realization , Not any Angular or React State management , Handle the commands calling the remote server and interpret the state change response from the server .
- The service layer provides two types of services for the view layer API:
- Public methods —— Call these methods to invoke commands on the remote server , Or change the state of the client .
- Public event flow —— Implemented as a RxJs Observable, It can be used by anyone who wants to be notified of a status change UI Component subscription .
- The view layer has only two simple responsibilities :
- Intercept UI Event and transform it into a public to the service layer API Method call .
- Subscribe to public API Observable, And make corresponding representation changes to the received notice .
A view - service - Server interaction example
Players can play a card by clicking on the face
A little bit more specific , Let's see how to play a card .
We assume that Player_X Will play the next card .Player_X Click on “ Heart A” Card face , This UI Events will trigger “Player_X Hit the heart A” This action .
Here are the steps the application will go through :
The view layer intercepts user generated events , And call the service layer playCard Method , Parameter is “ Heart A”.
The service layer sends messages to the remote server “Player_X Hit the heart A”.
The remote server updates the status of the game , And notify all clients that the state has changed . for example , It tells all clients Player_X Which card was played and who was the next player to play .
The service layer of each client receives the status update message sent by the remote server , And pass Observable Stream into notification of specific events . for example ,Player_X Received by the client service layer of isMyTurnToPlay by false, because Player_X Definitely not the next player . If the other player is Player_Y, Player_Y Received by the service layer of the client isMyTurnToPlay It will be true.
Each client's view layer subscribes to the event stream published by the service layer , And react to the event notification , Update on demand UI. for example ,Player_Y( The next player ) The view layer of allows the client to play a card , Other players' clients will not have this action .
The interaction between view layer and service layer
Light components and heavy services
Based on these rules , We finally built “ Light components ”, It only manages UI concerns ( Represents and UI Event handling ), and “ Heavy service ” Is responsible for handling all logic .
most important of all ,“ Heavy service ”( Contains most of the logic ) Completely independent of what is used UI Framework or library . It does not rely on Angular It doesn't depend on React.
of UI More details about layers can be found in the appendix section of this article .
The benefits of doing this
What are the benefits of doing so ?
Of course not the portability between different frameworks and libraries . Once you choose Angular, It is unlikely that anyone will want to switch to React, vice versa , But there are still some advantages .
One advantage of this method is , If implemented thoroughly , It will standardize the way we develop the front end , And easier to understand . In the final analysis , This is only through customization ( The service layer is customized ) Design one-way information flow . Customization has a low level of abstraction , It's simpler , But it may take some effort “ Reinvent the wheel ” The price of .
however , The biggest benefit is that the application has better and easier testability .
UI Testing is very complicated , No matter which framework or library you use .
But if we convert most of the code to pure TypeScript Realization , Testing will become easier . We can use the standard test framework to test the core logic of the application ( Here we use Mocha), We can also handle complex test scenarios in a relatively simple way , We will discuss in the next section .
5 Automatically test real-time multi-user interaction scenarios
Scopone It's a four person game .
4 Clients must pass WebSocket Connect to a central server . An operation performed by a client , for example “ Play a card ”, Will trigger all client updates ( That is, the so-called side effects ).
This is a real-time multi-user Interaction scenario . This means that if we want to test the behavior of the entire application , You need to run multiple clients and a server at the same time .
How do we test these scenarios automatically ? We can use standard JavaScript Test libraries to test them ? Can we test them on an independent developer workstation ? These are the questions to be answered next . The fact proved that , All these things are possible , At least to a large extent it is possible .
What is the real-time multi-user Interaction scenario testing
Let's take a simple example , Suppose we want to test that the card distribution of all players at the beginning of the game is correct . After the new game starts , All clients receive... From the server 10 card (Scopone The game has 40 card , Each player can get 10 Zhang ).
If we want to be in a separate machine ( such as , Developer's machine ) Automatically test this behavior on , You need a local server . We can do that , Because the server can be used as a local container or WebSocket Server running . therefore , We assume that there is a local server running on our machine .
however , To run tests , We also need to find a way to create the right context and actions that can trigger the side effects we want to test ( The distribution of cards is a side effect of a player starting the game ). let me put it another way , We need to find a way to simulate the following :
- 4 Players launch applications and join the same game ( Create the right context );
- A player starts the game ( Trigger the side effects we want to test ).
Only in this way can we check whether the server will issue the expected cards to all players .
A test case for a multi-user scenario
6 How to simulate multiple clients
Each client consists of a view layer and a service layer .
Service layer API( Methods and Observable flow ) Is defined in a class (ScoponeServerService class ).
Each client creates an instance of this class , And connect to the server . The view layer interacts with its service class instances .
If we want to simulate 4 A client , create 4 Different instances , And connect them all to our local server .
establish 4 Service class instances , representative 4 Different clients
How to create a context for a test
Now? , We have it. 4 Clients already connected to the server , We need to build the right context for the test . We need to 4 Players , And wait for them to join the game .
Create context for the test
Last , How to perform tests
In the creation of 4 After two clients and the correct context , Then we can run the test . We can have a player send a command to start the game , Then check whether each player has received the expected number of cards .
Run the test
Close together
The test of multi-user Interaction scenario is as follows :
- Create a service instance for each user ;
- Send commands to the service in the correct order , Create test context ;
- Send the command to trigger the side effect ( Is the command being tested );
- Verify the of each service Observable API Notice given , That is, the result of the command ( side effect ), Whether the expected data is included .
This is the service layer API Of BDD
We can think of this approach as targeting the service layer API Behavior driven development (BDD) test .
according to BDD The specification of , The test behavior is like this :
- Assume the initial scenario :4 Players join the game ;
- Time : Players start playing ;
- then : We want every player to get 10 card .
The test function uses a DSL Compiling , It consists of some special auxiliary functions , The combination of these functions creates the context (playersJoinTheGame Is an example of an auxiliary function ).
It is not an end-to-end test , But it can be very powerful
This is not a complete end-to-end test . We did not test the view layer .
But it can still be a very powerful tool , Especially if we insist “ Light components and heavy services ” The rules of .
If the view layer consists of light components , And most of the logic is concentrated in the service layer , Then we can cover the core of the application behavior , Whether it is client-side or server-side , We only need to make relatively simple settings , Use standard tools ( We used Mocha Test library , It's definitely not the latest and brightest frame ), And on the developer's machine .
The advantage of this is , Developers can write test suites that can be executed quickly , Increase the frequency of test execution . meanwhile , Such a test suite actually tests the entire application logic from the client to the server ( Even multi-user real-time applications ), Provides a high level of credibility .
7 Conclusion
Developing card games is an interesting experience .
Besides bringing me some fun during the outbreak , It also gives me the opportunity to explore architectural concepts through code .
We often use architectural concepts to express our views . I find , Put these concepts into practice , Even simple proof of concept , It will also increase our understanding of them , Let's have more confidence in using them in real projects .
8 appendix : View layer mechanism
The components in the view layer mainly do two things :
- Handle UI Events and convert them to service commands .
- Subscribe to the stream exposed by the service , And update UI To respond to events .
In order to explain the meaning of the last point more specifically , Let's give you an example : How to determine who is the next player to play .
As we said , One of the rules of the game is that players can play one card after another . for example , If Player_X Is the first player ,Player_Y Is the second player , So in Player_X After playing a card , Only Player_Y To play the next card , No other player can play cards . This information is part of the state maintained by the server .
Each time a card is played , The server will send a message to all clients , Specify who the next player is .
The service layer passes through a process called enablePlay Of Observable The flow converts messages into notifications . If the message says that the player can play the next card , The service layer through enablePlay The value of the notification is true, Otherwise, it would be false.
Components that allow players to play cards must be subscribed enablePlay$ flow , And respond accordingly to the notified data .
In our React In the implementation , This is a called Hand Functional components of . This component defines a state variable enablePlay, Its value represents the probability of playing cards .Hand Component subscribes to enablePlay Observable flow , Whenever it receives enablePlay At the notice of , By setting enablePlay To trigger UI Repaint .
Here's how to use React Of Hand Component to implement this specific function .
export const Hand: FC = () => {
const server = useContext(ServerContext);
. . .
const [handReactState, setHandReactState] = useState<HandReactState>({
. . .
enablePlay: false,
});
. . .
useEffect(() => {
. . .
. . .
const enablePlay$ = server.enablePlay$.pipe(
tap((enablePlay) => {
setHandReactState((prevState) => ({ ...prevState, enablePlay }));
})
);
const subscription = merge(
. . .
handClosed$
).subscribe();
return () => {
console.log("Unsubscribe Hand subscription");
subscription.unsubscribe();
};
}, [server]);
. . .
return (
<>
. . .
<Cards
. . .
enabled={handReactState.enablePlay}
></Cards>
. . .
</>
);
};Angular Version logic is the same , And in HandComponent Implemented in . The only difference is that enablePlay$ Observable The subscription of the stream is directly through the template async Pipeline completed .
Author's brief introduction :
Enrico Piccinin For code and IT Interested in the odd things that happen occasionally in the organization . By virtue of IT Years of experience in the development field , He wished to know that “ new IT” What happens in a traditional organization . You can enricopiccinin.com or LinkedIn Found on the Enrico.
Link to the original text :
https://www.infoq.com/articles/exploring-architecture-building-game/
边栏推荐
- uniapp设置页面跳转效果 - navigateTo切换效果 - 全局animationType动画
- sqlmap检测SQL-lab靶场
- In depth research and analysis report on global and Chinese diet food market
- Check box select all or deselect all
- 111. minimum depth of binary tree
- In depth research and analysis report on global and Chinese one component liquid rubber Market
- My struggle: my years in foreign enterprises (II)
- Precision alignment adjustment platform
- Seven parameters of thread pool and reject policy
- In depth research and analysis report on global and Chinese plant extract products market
猜你喜欢

Individual income tax rate table

Leetcode 1968. 构造元素不等于两相邻元素平均值的数组(可以,终于解决)

IC fresh Chinese cabbage price of 400000 yuan! Experienced experts who have worked for many years guide you how to choose an offer!

North China pushed Yale hard, MIT won the first place in a row, and the latest 2023qs world university ranking was released

HMS core shows the latest open capabilities in mwc2022, helping developers build high-quality applications

大道至简 | 设计 ViT 到底怎么配置Self-Attention才是最合理的?

Task manager based on Qt development

Avenue to simplicity | how to configure self attention for vit is the most reasonable?

CVPR 2022 | neural radiation field geometry editing method nerf editing

Uniapp settings page Jump effect - navigateto switching effect - Global animationtype animation
随机推荐
大道至简 | 设计 ViT 到底怎么配置Self-Attention才是最合理的?
Task manager based on Qt development
树莓派获得网络安装系统功能,无需借助其他设备
提取式存储才是最佳的记忆方法
airtest自动化测试
PowerShell主架构师:我用业余时间开发项目,表现优秀反而被微软降级了
North China pushed Yale hard, MIT won the first place in a row, and the latest 2023qs world university ranking was released
Raspberry pie obtains the function of network installation system without the help of other devices
Live800: several ways for intelligent customer service to improve customer experience
Current situation and future development trend of global and Chinese metal casting robot market
In depth research and analysis report on global and Chinese hydrogen fuel station market
非常值得学习的调度开源库推荐
Chip engineers are too expensive? Your sister
MySQL create table error 1067 - invalid default value for 'update_ time‘
Seven parameters of thread pool and reject policy
SQL数据查询之单表查询
线程池的七个参数与拒绝策略
sqlmap检测SQL-lab靶场
.NET C#基础(6):命名空间 - 有名字的作用域
[public class preview]: mxplayer Ott audio and video transcoding practice and optimization