CW-MODEL

有需要的朋友qq

1703105484

In this first task we will ask you to implement Java classes, which model the game mechanics of "Scotland Yard" within a given software framework.

Note that you will implement the full version of the game (not the beginners version), but with the following alterations/clarifications:

  • Police or Bobbies will not be modelled.
  • The Ferry will be modelled.
  • Mr X and the detectives will be given variable (user-specified) amounts of tickets at the start, the normal rules for tickets follow:
    • When a detective moves, the ticket used will be given to Mr X.
    • Used Mr X tickets are discarded.
  • The number of rounds in a game is variable (>0) specified by an initial setup rather than fixed to 22 rounds as in the board game.
    • In the manual, the round count is defined as the number of transitions between Mr X and the detectives as a whole, this number is different from the number of slots on Mr X's Travel Log because Mr X can use double moves which occupies two slots (e.g. a 22 round game with two double move tickets means Mr X can have up to 24 moves).
    • For practical reasons, we've simplified this rule so the game can be set up with a variable max number of moves for Mr X (i.e. the slot count in Mr X's travel log), such that the game is over when Mr X's travel log is completely full, instead of some abritary number of rounds.
  • Mr X cannot move into a detective location.
  • Mr X loses if it is his turn and he cannot make any move himself anymore.
  • Detectives lose if it is their turn and none of them can move, if some can move the others are just skipped.
  • Ticket.SECRET represents a black ticket, this is used for Mr X's secret moves

Pay special attention to rules for double moves and secret moves, since these are particularly complex.


  • Download a copy of the full rulebook from Ravenburger's website
  • Download the Java version of the game

    • macOS (aarch64, for M1 Mac ONLY) - sy-remote-win_x86_64-linux_x86_64-mac_aarch64.jar
    • Windows, Linux, macOS (x86_64, anything that's not M1) - sy-remote-win_x86_64-linux_x86_64-mac_x86_64.jar

The executable is a JAR file, run it on your machine like so:

 java -jar <path_to_the_game_jar_file_you_have_just_downloaded_from_above>

This game is the online version with an offline mode in the menu (accessible via Game | Local game).

For the offline mode, follow the instructions in this video where Sion plays against himself. For the online mode, you will need credentials which are available here. Search for your UoB username in the 3rd column and then note down your personal username (integer on in 1st column) and password (alphanumeric string in 2nd column). Do not use personal data for your team's name when connecting to the game server: make up something fun!

  • How do I know I am using an M1 (Apple Silicon) Mac?

    • Click on the Apple logo on the menu bar and click on About This Mac. in the Chip section, if you see something that starts with Apple, you are using an Apple Silicon Mac; if it starts with Intel, you are using an Intel Mac.
    • For Apple Silicon Mac users, you should look for the following fields when downloading softwares: Apple Siliconarm64aarch64Arm MacM1 Mac
    • For Intel Mac users, you should look for the following fields when downloading softwares: x86_64x64AMD64Intel Mac
  • Connecting to the online game server via SOCKS5 proxy. (OPTIONAL) here

TODO: Familiarise yourself with the rules of the Scotland Yard board game and organise some sessions to play the game!


Getting Started

This is a pair programming exercise, so you are strongly recommended to use version control software such as Git and work in your team using a private online repository. Try to do as much pair programming (one screen, two minds) as possible.

This project uses Maven as a build system. You do not need to understand the inner workings of Maven, but feel free to read up about it here.


TODO: Start by creating a repository with the skeleton code from this zip file in it:

  • cw-model

If you use Git, a .gitignore file is already present with all the correct files to ignore.


TODO: Setup the Project

  • IntelliJ - follow the import guide here . The main test class is uk.ac.bris.cs.scotlandyard.model.AllTest, the main class to start the UI is uk.ac.bris.cs.scotlandyard.Main.

  • CLI - type the following command at project root (use PowerShell on Windows):

    ./mvnw clean compile
    

Project Structure

The project's main source files are all located in src/main/java and organised in directories according to the package name, for example uk.ac.bris.cs.scotlandyard.model.ScotlandYard is a file located at src/main/java/uk/ac/bris/cs/scotlandyard/model/ScotlandYard.java.

The main focus of this project is to write a working Scotland Yard game model, thus your work will focus around the uk.ac.bris.cs.scotlandyard.model package. You will only need to edit two classes: MyGameStateFactory.java and MyModelFactory. You are allowed to add new classes to the package. You are not allowed to modify any of the interfaces or tests.

Testing

There are 82+ tests provided for your development. They are located in src/test/java and organised in the same directory pattern. You should try to run the tests on the provided skeleton project.

TODO: Test the empty model and observe test failures:

  • IntelliJ (Recommended) - Locate the class uk.ac.bris.cs.scotlandyard.model.AllTest in IntelliJ and right click the green play button in the left-hand side gutter (i.e. where the line number is). IntelliJ should run all the tests and present you with a test report.

  • CLI - type the following command at project root:

    ./mvnw clean test
    

    The result will look something like this:

    Results :Failed tests: ...
    Tests in error: ....
    Tests run: 81, Failures: .., Errors: .., Skipped: 0
    ...
    

Some of the tests are written using AssertJ to simplify the statement of assertions. It will be sufficient for completing this exercise to just read the test names and assertion statements to understand what the tests are testing.

While implementing the model, you may only want to focus on one particular test subset.

  • IntelliJ (Recommended) - each test should have a green play button on the left; clicking on it should run that specific test.

  • CLI - run a single test class by specifying the test argument when calling Maven, for example:

    ./mvnw -Dtest=GameStateCreationTest test
    

    You can also run a specific test case in a test class, for example:

    ./mvnw -Dtest=GameStateCreationTest#testNullMrXShouldThrow* test
    

Development

For help and guidance with your development and how to get started, take a look at the guide now.

TODO: Pass all tests.

When you're done with the model implementation, you can start the GUI and play your very own Java version of Scotland Yard.

TODO: Start up the GUI and enjoy!

  • Intellij - locate the class uk.ac.bris.cs.scotlandyard.Main, press the play button next to the class declaration.

  • CLI - type the following command at project root:

    ./mvnw clean compile exec:java
    

If everything works and you can complete games then you have a working Scotland Yard model and have completed the CW-MODEL coursework! Before embarking on the next step make sure you have produced a bug-free, stable, well coded and well documented model, which you understand well. Make sure you take some time again to review the object-orientation concepts covered in the course and used in your implementation so you are ready for your presentation and VIVA.

Stage 2 of the coursework (cw-wi) will be released at a later date.

Find node tool

To make the development process smoother, a simple Find node tool is included in the GUI. The tool does not require a working model, however, you need to make sure the project still compiles otherwise the GUI won't start of course.

The Find node tool is located in the menu: Help | Find node, you should see a window that looks like this:

Type in the node you want to find in the search bar, you may enter multiple nodes separated by spaces, e.g 42 42 44 45. The map supports panning and zooming just like the main game.

Implementation Guide

Recommended reading:

  • Scotland Yard game rulebook
  • Guava's Immutable collections page
  • Guava's ValueGraph section
  • JavaDocs for cw-model, also available as comments in the skeleton

Look around in the uk.ac.bris.cs.scotlandyard.model package, you can complete this project part by only using classes from this package and Guava + JDK standard class library.

FACTORY

To begin, locate the skeleton class uk.ac.bris.cs.scotlandyard.model.MyGameStateFactory. This is the main class you need to implement to model the behaviour of our Scotland Yard game. As the name tells us, this class is a factory that implements the Factory<GameState> interface. This means that it has a factory method of some sort (build in this case) which returns a new instance of GameState. As we cae see, this method does indeed exist. However, we find a placeholder in the position where an implementation needs to be placed:

// TODO
throw new RuntimeException("Implement me");

GAME STATE

When you implement a method, you should remove the placeholders as those are only present to facilitate compilation. Next have a look at the Java documentation for the uk.ac.bris.cs.scotlandyard.model.Board.GameState interface. We see that GameState extends Board and thus will have to implement 8 methods; 7 inherited from Board plus the advance method it requires. Your factory will need to return an implementation of this interface. Let this implementation class, which implements GameState, be called MyGameState. Since we only ever need the MyGameState class to be accessible from the factory, we can implement it as an inner class of MyGameStateFactory. In addition, consider that the class can be private and final. Adding our 8 methods with placeholder returns of null and defining required imports leads to compiling code again:

package uk.ac.bris.cs.scotlandyard.model;import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.*;
import javax.annotation.Nonnull;
import uk.ac.bris.cs.scotlandyard.model.Board.GameState;
import uk.ac.bris.cs.scotlandyard.model.Move.*;
import uk.ac.bris.cs.scotlandyard.model.Piece.*;
import uk.ac.bris.cs.scotlandyard.model.ScotlandYard.*;public final class MyGameStateFactory implements Factory<GameState> {private final class MyGameState implements GameState {@Override public GameSetup getSetup() {  return null; }@Override  public ImmutableSet<Piece> getPlayers() { return null; }@Override public GameState advance(Move move) {  return null;  }}
}

ATTRIBUTES

Next lets start thinking about what state data MyGameState needs to hold and define some first attributes. Exploring the getter methods, which we just defined, tells us that we at least need to hold:

  1. The GameSetup to return it, as well as have access to the game graph and Mr X reveal moves
  2. Player to hold the MrX player and a List<Player> to hold the detectives
  3. List<LogEntry> to hold the travel log and count the moves Mr has taken
  4. Set<Move> to hold the currently possible/available moves
  5. Set<Piece> to hold the current winner(s)

We also may want to keep track of which Piece can still move in the current round, and which Pieces and Players are in the game. Note that many of the collections to be used should be immutable and private as good defensive programming would prescribe, leading to a number of attributes such as:

...private final class MyGameState implements GameState {private GameSetup setup;private ImmutableSet<Piece> remaining;private ImmutableList<LogEntry> log;private Player mrX;private List<Player> detectives;private ImmutableSet<Move> moves;private ImmutableSet<Piece> winner;...
}

CONSTRUCTOR

Let us now move on and write a constructor for MyGameState (consider, once done, which of our attributes can be be made final). Our constructor will be called by the build method of the outer class MyGameStateFactory, thus it should make use of at least the information available there:

  1. The game setup
  2. The Player mrX
  3. The ImmutableList<Player> detectives

In addition, we should provide the remaining players (just MrX at the starting round) and the current log (empty at the starting round) so that the constructor can complete a full initialisation of the game state. Our constructor could, considering the incoming parameters as immutable, start off like this:

...
private MyGameState(final GameSetup setup, final ImmutableSet<Piece> remaining, final ImmutableList<LogEntry> log,final Player mrX,final List<Player> detectives){ ... }
...

Note that the constructor is private since only the builder in the enclosing class (and later the advance method) will be calling it. Now that we have at least the declaration of a MyGameState constructor available, we should return a new instance of MyGameState in the build method of MyGameStateFactory:

...
@Nonnull @Override public GameState build(...){return new MyGameState(setup, ImmutableSet.of(MrX.MRX), ImmutableList.of(), mrX, detectives);
}
...

INITIALISATION

So far, we took care of an appropriate structure for our implementation, but did not aim at passing any tests yet. We now move on to implementing the constructor in order to check and initialise fields using the parameters passed into the constructor. First, we could initialise the local attributes that are directly supplied by the parameters:

private MyGameState(...){...this.setup = setup;this.remaining = remaining;this.log = log;this.mrX = mrX;this.detectives = detectives;...
}

Add appropriate checks that these parameters handed over are not null and you will pass your first tests in GameStateCreationTest. Start working your way through the tests guiding your implementation. You may add checks, entire methods, fields, or even classes as you see fit. For instance, the test #testEmptyMovesShouldThrow demands that there is at least one move to play, otherwise an IllegalArgumentException should be thrown. Thus, you should add a check in the constructor like this:

...
if(setup.moves.isEmpty()) throw new IllegalArgumentException("Moves is empty!");
...

GETTERS

Some further checks and tests will be required in the constructor, including checks that all detectives have different locations, that detectives in the list are indeed detective pieces, MrX is indeed the black piece, and that there are no duplicate game pieces. Let the tests guide you in this regard. Having defined our attributes, we can also start returning values in our getter methods ( leave the getWinner method until later). Note that some getter methods need to return Optional values:

...
@Override public GameSetup getSetup(){ return setup; }
@Override public ImmutableList<LogEntry> getMrXTravelLog(){ return log; }
@Override public Optional<Integer> getDetectiveLocation(Detective detective){// For all detectives, if Detective#piece == detective, then return the location in an Optional.of();// otherwise, return Optional.empty();
}
...

AVAILABLE MOVES

Not all values can be easily assembled - getAvailableMoves() for instance requires us to find all moves Players can make for a given GameState. It will be easiest to calculate these moves upfront in the constructor of a GameState and store them in moves, but for such a complex task it is recommended to use some helper methods to avoid monolithic code and an overlong constructor. One helper function could be the calculation of single moves, thus consider the below snippet of code as a start and inspiration on how to implement valid move generation:

...
private static Set<SingleMove> makeSingleMoves(GameSetup setup, List<Player> detectives, Player player, int source){// TODO create an empty collection of some sort, say, HashSet, to store all the SingleMove we generatefor(int destination : setup.graph.adjacentNodes(source)) {// TODO find out if destination is occupied by a detective//  if the location is occupied, don't add to the collection of moves to return      for(Transport t : setup.graph.edgeValueOrDefault(source, destination, ImmutableSet.of()) ) {// TODO find out if the player has the required tickets//  if it does, construct a SingleMove and add it the collection of moves to return  }// TODO consider the rules of secret moves here//  add moves to the destination via a secret ticket if there are any left with the player}// TODO return the collection of moves
}...

However, the above code is only a starting point for an implementation since DoubleMoves have to be implemented too, and they are more tricky to handle. Once moves and all exposed state is computed and returned by the getter methods you will pass more tests. Implementing GameStage#getAvailableMoves correctly should help pass through GameStateMrXAvailableMovesTest and GameStateDetectivesAvailableMovesTest.

ADVANCE

Our attention can now shift towards the GameState#advance method, whose task it is to return a new state from the current GameState and a provided Move. The GameState#advance method is central to the behaviour of a game, and most tests depend on this method to verify behaviours of the players. This is the hardest part to implement, so you may want to break up some of this logic into separate, smaller private methods. The first thing we must check is that the provided move is indeed valid using code similar to:

...
public GameState advance(Move move){if(!moves.contains(move)) throw new IllegalArgumentException("Illegal move: "+move);...
}
...

VISITOR PATTERN

Next, we need to implement different behaviours for applying SingleMoves and DoubleMoves, e.g. we may need destination or destination2 for enacting moves. To route these different implementations and find out about the Move type we can use the visitor pattern. Familiarise yourself with the interface Move, which has a generic accept method to support the Visitor design pattern:

public interface Move extends Serializable {...<T> T accept(Visitor<T> visitor);...
}

Note that this is very similar to the house visitor we looked at in previous weeks (tasks, solutions). If you have not completed those exercises, it's highly recommended you do so (or at the very least look at the solutions) to understand how the visitor pattern works. If you understand the house visitor, you more or less already understand the move visitor. Just as we used the visitor pattern to distinguish StrawHouses, StickHouses, and BrickHouses, we can use it to distinguish SingleMoves and DoubleMoves.

The generic House.Visitor<T> interface is very similar to Move.Visitor<T>, and the corresponding accept() methods are effectively identical, as we can see by comparing the implementations in BrickHouse and SingleMove (very slightly modified for clarity):

...public class BrickHouse extends House {...public <T> T accept(House.Visitor<T> visitor) { return visitor.visit(this); }
}final class SingleMove implements Move {...public <T> T accept(Move.Visitor<T> visitor) { return visitor.visit(this); }
}

Consequently, we can get access to a particular SingleMove or DoubleMove by supplying a Visitor<...> object as a parameter to the move.accept(...) method. This parameter could be an anonymous inner class instantiation such as:

... = move.accept(new Visitor<...>(){@Override public visit(SingleMove singleMove){ ... }@Override public visit(DoubleMove doubleMove){... }
});

Equally, it could be defined in the normal way using a separate file and creating a named class which implements the Move.Visitor<T> interface, or any of the other ways demonstrated in the house visitor task.

STATE UPDATE

With access to the particular move to enact we can now implement the update of the state, which means returning a new GameState object (since the old one is widely immutable) at the end of the advance method. This returned state should be updated with regard to: 1) player locations, 2) tickets used and handed over to players, 3) travel log if move.commencedBy is MrX, and 4) remaining pieces in play for the current round (and if none remain an initialisation of players for the next round).

This returned state should somehow be updated in the following way (not necessarily in order):

  • If it's Mr X's turn (which can be checked using move.commencedBy):

    • Add their move(s) to the log

      • If a move should be revealed according to the GameSetup, reveal the destination in the log, otherwise keep the desination hidden
    • Take the used ticket(s) away from Mr X
    • Move Mr X's position to their new destination
    • Swap to the detectives turn
  • If it's the detectives' turn:
    • Move the detective to their new destination
    • Take the used ticket from the detective and give it to Mr X
    • Ensure that particular detective won't move again this round (i.e. when getAvailableMoves() is called, it won't include any moves from that detective)
    • If there are no more possible detective moves, swap to Mr X's turn

Make sure to refer back to the Scotland Yard game rulebook, as well as the alterations/clarifications at the top of the page, for more details on the game logic.

A correctly implemented advance method should pass most tests in GameStatePlayerTest and GameStateMoveTest.

DETERMINE WINNER

Once the selection of moves and the advancement of the GameState are implemented, the game will need to determine if someone has won or not. This can again be done in the constructor of GameState; if no winner has been determined yet then getWinner() should return an empty set. In any case, implement checks for end game conditions and return winners in getWinner() and your implementation should then pass most tests in GameStateGameOverTest.

The detectives win, if:

  • A detective finish a move on the same station as Mr X.
  • There are no unoccupied stations for Mr X to travel to.

Mr X wins, if:

  • Mr X manages to fill the log and the detectives subsequently fail to catch him with their final moves.
  • The detectives can no longer move any of their playing pieces.

FINALISE GAMESTATE

To finalise your implementation take note of all the methods provided in classes from package uk.ac.bris.cs.scotlandyard.model. Read the JavaDocs carefully as most are designed to help you implement your GameState in some way. Keep in mind that some tests depend on certain methods such as advance to be correct in order to run further assertion down the line. It is highly recommended that you implement your GameState in the following sequence:

  1. The constructor of GameModel, including any validation on the parameters.
  2. All getters, excluding getWinner. and getAvailableMoves.
  3. The advance method, together with getAvailableMoves.
  4. The getWinner method.

OBSERVER

Finally, implementing observer-related features in the file uk.ac.bris.cs.scotlandyard.model.MyModelFactory will pass most tests in ModelObserverTest. This class is a factory again, producing via build(...) a game Model which should hold a GameState and Observer list and can be observed by Observers with regard to Events such as MOVE_MADE or GAME_OVER. Reviewing lecture slides on the observer design pattern should be sufficient to get going. The chooseMove(...) method is called when a move has been chosen by the GUI. It could call the advance(...) method, check if the game is over, and inform the observers about the new state and event similar to the code below:

...
@Override public void chooseMove(@Nonnull Move move){// TODO Advance the model with move, then notify all observers of what what just happened. //  you may want to use getWinner() to determine whether to send out Event.MOVE_MADE or Event.GAME_OVER
}

Now all tests including GameStatePlayoutTest and ModelObserverTest should pass, those tests contain several full game play-outs. If everything works and you can start the GUI (see above) and complete games then you have a working Scotland Yard model and have completed the CW-MODEL coursework! Before embarking on the next step make sure you have produced a bug-free, stable, well coded and well documented model, which you understand well. Make sure you take some time again to review the object-orientation concepts covered in the course and used in your implementation so you are ready for your presentation and VIVA. Once you are confident, you can take a look at the final open-ended task cw-ai - which will be released later.

scotland yard相关推荐

  1. 无需训练RNN或生成模型,我写了一个AI来讲故事

    作者 | Andre Ye 译者 | 弯月 出品 | AI科技大本营(ID:rgznai100) 这段日子里,我们都被隔离了,就特别想听故事.然而,我们并非对所有故事都感兴趣,有些人喜欢浪漫的故事,他 ...

  2. DeepMind将博弈论融入多智能体研究,让纳什均衡变得更简单

    雷锋网 AI 科技评论按,随着 AI 系统在现实生活中变得越来越重要,我们自然该探索不同系统间的交互方式了,这些多智能体间到底用了什么独特的方式呢? 在 DeepMind 的最新论文(发表在 Scie ...

  3. 无需训练 RNN 或生成模型,如何编写一个快速且通用的 AI “讲故事”项目?

    作者 | Andre Ye 译者 | 弯月,责编 | 郭芮 头图 | CSDN 下载自视觉中国 出品 | CSDN(ID:CSDNnews) 以下为译文: 这段日子里,我们都被隔离了,就特别想听故事. ...

  4. English and Programming_Day1

    Day_1 Everything you need to know about Julian Assange 关于朱利安.阿桑奇你所需要知道的一切 Who is Julian Assange? 谁是朱 ...

  5. java面试常见设计模式

    java面试常见设计模式 看这里,动画描述很好 创建型模式 工厂方法模式 目的 结构 场景 优缺点 示例代码 button factory Demo.java: 客户端代码 抽象工厂模式 目的 结构 ...

  6. 英语之境(chapter two)

    -------------------------------------------------------June 21th Thursday CHAPTER TWO 第二章 THE teleph ...

  7. 2021WSB-day2-1 - Anil Jain教授讲述了生物特征识别的定义,为何用生物特征识别,以及过去,现在和将来

    听百家之言,集百家智慧,站在巨人肩上攀登 来自MSU的Anil Jain教授讲述了生物特征识别的定义,为啥用生物特征识别,以及过去,现在和将来. 生物特征识别的:过去 现在 将来 文章目录 什么是生物 ...

  8. PL/SQL用户自定义记录(record)操作实例讲解

    用户自定义记录 PL/SQL提供了一个用户定义的记录类型,允许定义不同的记录结构.记录由不同的字段组成. 记录类型定义为: TYPE type_name IS RECORD( field_name1 ...

  9. 工厂中的流水线竟然是活生生的建造者模式

    看个图放松一下   从度娘上找了一个动图,很清楚的可以看到这是一条流水线.黄色衣服的工人负责将盒子放进袋子里,蓝色工人负责将袋子密封.流水线大家应该都清楚,就是每个人专门负责一道工序,一个完整的产品需 ...

最新文章

  1. FPGA优化之高扇出
  2. 如何使用Visual Studio Code作为Git的默认编辑器
  3. Silverlight调用的JS方法返回对象数组的处理方法
  4. 论软件的模块化与架构
  5. 10亿用户之后,为什么是百度率先打破花园围墙?
  6. MyBatis教程– CRUD操作和映射关系–第1部分
  7. android OEM unlocking分析
  8. Hadoop的Python框架指南
  9. linux进程和线程理解
  10. 大哥特斯拉:造车“三傻”,咱们抱团?
  11. linux mediainfo,Ubuntu安装MediaInfo
  12. Android Studio Xposed模块编写(二)
  13. 捷联惯导数值更新算法-姿态更新+速度更新+位置更新
  14. 百度文库解决复制问题
  15. groovy 变量和字符串
  16. python 基于Tkinter的姻缘测试器
  17. 惊天大突破!「我国数学家证明 NP=P」!道翰天琼认知智能机器人平台API接口大脑为您揭秘。
  18. 潜入浅出--通信中的频带利用率,以MASK.MPSK作为例子
  19. 获取Avrix上Computer Vision and Pattern Recognition的论文,进一步进行统计分析。
  20. discuzX3.4增加用户名注册和登录长度限制(终极版,实测有效,附带详细数据库修改策略)

热门文章

  1. QTableWidget中如何清空行,并保持行仍可再写入数据
  2. Python 中的多进程(进程之间的通信)
  3. UI设计 AndroidIOS开发推荐用色
  4. 【图像去噪】兴智杯论文复现赛——NAFNet
  5. C++11中的时间库std::chrono(引发关于时间的思考)
  6. win10磁盘占用100%
  7. AD常用使用快捷键和技巧
  8. 玩游戏用什么轴的机械键盘好_机械键盘轴哪个最适合打游戏
  9. 学计算机键盘用什么轴,机械键盘别再盲目的选择,看看四种常用轴到底适合什么用途...
  10. JVM--Java虚拟机