CHI-FENG LIU
  • NEW!
    NEW!  Connect Ideas Across Notes
    Save time and share insights. With Paragraph Citation, you can quote others’ work with source info built in. If someone cites your note, you’ll see a card showing where it’s used—bringing notes closer together.
    Got it
      • Create new note
      • Create a note from template
        • Sharing URL Link copied
        • /edit
        • View mode
          • Edit mode
          • View mode
          • Book mode
          • Slide mode
          Edit mode View mode Book mode Slide mode
        • Customize slides
        • Note Permission
        • Read
          • Only me
          • Signed-in users
          • Everyone
          Only me Signed-in users Everyone
        • Write
          • Only me
          • Signed-in users
          • Everyone
          Only me Signed-in users Everyone
        • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invite by email
        Invitee

        This note has no invitees

      • Publish Note

        Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

        Your note will be visible on your profile and discoverable by anyone.
        Your note is now live.
        This note is visible on your profile and discoverable online.
        Everyone on the web can find and read all notes of this public team.

        Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Explore these features while you wait
        Complete general settings
        Bookmark and like published notes
        Write a few more notes
        Complete general settings
        Write a few more notes
        See published notes
        Unpublish note
        Please check the box to agree to the Community Guidelines.
        View profile
      • Commenting
        Permission
        Disabled Forbidden Owners Signed-in users Everyone
      • Enable
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Suggest edit
        Permission
        Disabled Forbidden Owners Signed-in users Everyone
      • Enable
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
      • Emoji Reply
      • Enable
      • Versions and GitHub Sync
      • Note settings
      • Note Insights New
      • Engagement control
      • Make a copy
      • Transfer ownership
      • Delete this note
      • Save as template
      • Insert from template
      • Import from
        • Dropbox
        • Google Drive
        • Gist
        • Clipboard
      • Export to
        • Dropbox
        • Google Drive
        • Gist
      • Download
        • Markdown
        • HTML
        • Raw HTML
    Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Engagement control Make a copy Transfer ownership Delete this note
    Import from
    Dropbox Google Drive Gist Clipboard
    Export to
    Dropbox Google Drive Gist
    Download
    Markdown HTML Raw HTML
    Back
    Sharing URL Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Customize slides
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # GoGui Note ## TODO: Add Games - Bugs - [ ] 按 clear board 後視窗會跳走 - [x] Hex 顏色錯誤 (需要將棋盤轉 90°) - [x] 位置判定錯誤 (按紅點會下在 A) ![](https://hackmd.io/_uploads/rJOuDrHFh.png) - [ ] crash when using hex gui to import sgf: ``` (;FF[4]CA[UTF-8]AP[gogui-twogtp:1.4.9]SZ[11]KM[0]PB[minizero]PW[MoHex]DT[2023-07-25];B[gd];W[ee];B[ih];W[ii];B[he];W[hh];B[bg];W[gf];B[gh];W[cd];B[ci];W[bc];B[dc];W[gg];B[ab];W[ac];B[ca];W[ii]) ``` - [x] Add gogui-client - [ ] no winning message in hex - win by AI - [Commit: Set default game to Go](https://github.com/rlglab/gogui-general/commit/b4f6488c890485be464767edb1fb9bf8ae35b412#diff-11853de813a9ceb23d03f3cbc05e9078b48f98a04509415010ba792727a225ebL333) - [x] 預設遊戲沒打勾 ![image.png](https://hackmd.io/_uploads/BkG_r0ZXT.png) - [x] 用棋譜開 GoGui 時棋譜會被洗掉 - [ ] Hex new game 大小會變大 ![image.png](https://hackmd.io/_uploads/H1DJDAbQ6.png) - [ ] 六角形間有縫隙 ![image.png](https://hackmd.io/_uploads/r1JYoA-X6.png) - 打開程式時預設遊戲為上次關閉時的遊戲 - 加遊戲 - 現有: - Go - OOG - Gomoku - Hex - Othello - 現有框架就可以做的 (★) - NoGo - Havannah - Connect4 - Nonogram - KillallGo - 一次落好幾顆子 (★★) - Connect6 - Dots and Boxes - 選棋子+移動 (★★★) - chess - Amazon - Breakthrough - Chinese Chess - Shogi - lines of action - 跳棋 (棋盤有特殊形狀, multi-players) - 選棋子+移動+落子 (★★★★) - slither - 有機率的環境 (★★★★★) - 2048 - Chinese Dark Chess - 擺棋子 - 愛因斯坦棋 - Atari Games (single player) - 每個遊戲的狀態要分開保存 - 改寫 `GoGui.java` 中與遊戲狀態相關的函數的路徑,分別加上 `m_rule.gameName()` ```java=4238 private void restoreMainWindow(int size) ``` ```java=4344 private void saveSession() ``` - 額外功能 - 修改電腦落子時的提示音 (預設為系統提示音) ## Installation 1. Download [NetBeans](https://netbeans.apache.org/) IDE 2. Clone the code from [github](https://github.com/iis-rlg-lab/gogui-general). 3. Add new project. ![](https://i.imgur.com/FX7LEev.png) ![](https://i.imgur.com/DXIsnlw.png) ![](https://i.imgur.com/aq8kr7n.png) 4. Find the file that we just clone from github. ![](https://i.imgur.com/XzSQDQw.png) ![](https://i.imgur.com/lHQETOB.png) Finish!! Let's add a new game~ ## Add Game Four games have been pre-written, OOGomoku, Gomoku, Go, Hex. To learn how to add new games, we will use the addition of Hex game as an example. You can also check the [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7) on [GitHub](https://github.com/iis-rlg-lab/gogui-general). <a id="TODO-以Hex-game為例"></a> ### Using Hex game as an example: 1. [Add game rules](#新增遊戲規則) 2. [Plug the game into menu](#step2) 3. [Add a new game board (optional)](#step3) 4. [Add an interface for drawing different shape of field background (optional)](#step4) <a id="新增遊戲規則"></a> ## ### Add Hex game rule: [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7#diff-b84726bc0e34416a1586d096d6344ed83a3ce4030fbd577d9f527dd78c1df7d7) #### Step 1: create new class for new game rule in package net.sf.gogui.rule File: `net\sf\gogui\rule` ![](https://i.imgur.com/WiXvYOb.png) 1. When adding new game rules, inherit the Rule.java abstract class's interface to implement. 2. The main functions to implement are as follows: ```java=25 public abstract String gameName(); public abstract void reset(int boardsize); public abstract boolean isEnd(); public abstract boolean isLegalMove(Point p); public abstract List<Point> play(Point p); ``` - **getName**(): Return game name. - **reset**(): initialize. - **isEnd**(): To test whether one side wins. - **isLegalMove**(): To test whether the chess piece is a legal move。 - **play**(): Implement the game rule. Play this chess piece; if there are any changes on the board, e.g., a piece is captured, the color of pieces change, etc., return a list of pieces that need to be changed. If there are no change, return `null`. (more detials of `Point` see: [Rule.java](https://github.com/iis-rlg-lab/gogui-general/blob/main/src/net/sf/gogui/rule/Rule.java)) 3. The following message return function is actually optional, and the return message has been preset, and the developer can also define it by themselves. ```java=37 public String getEndMoveMainMessage() { return "Game finished"; } public String getEndMoveoptionalMessage() { return "The game is finished."; } public String getIllegalMoveMainMessage() { return "Play illegal move"; } public String getIllegalMoveoptionalMessage() { return "This move violates the rule."; } public String getIllegalMoveDestructiveOption() { return "Play illegal"; } ``` ![](https://i.imgur.com/YMNtOht.png) ![](https://i.imgur.com/1UPWyiD.png) - **getEndMoveMainMessage**(): Message title when the game ends. - **getEndMoveoptionalMessage**(): Message displayed when the game ends. - **getIllegalMoveMainMessage**(): Message title when an illegal move is made. - **getIllegalMoveoptionalMessage**(): Message displayed when an illegal move is made. - **getIllegalMoveDestructiveOption**(): Button name to choose when making an illegal move. <a id="step2"></a> ## ### Plug the game into menu #### Step 1: Add to game menu: [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7#diff-e19ed4d71c99bf60a8b61c49e703e5485d5c6a1f6623ad5a7e70bf3c723493e8) - GoGuiMenuBar File: `net\sf\gogui\gogui\GoGuiMenuBar.java` After writing the game rules and game board, add a new game menu for users to switch games. ![](https://i.imgur.com/3hGSTnf.png) ```java=427 menu.addRadioItem(gamesgroup, actions.m_actionOOGomoku); menu.addRadioItem(gamesgroup, actions.m_actionGomoku); menu.addRadioItem(gamesgroup, actions.m_actionGo); menu.addRadioItem(gamesgroup, actions.m_actionHex); // <TODO> ``` Use **addRadioItem** and **gamesgroup** to add the new game to the button group and input the action to be performed when the option is selected. ## #### Step 2: Construct the game: [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7#diff-0b16348700e9a39f668620c1c4e7f0d4cabd9f99c52e04a4730285d090077c32) - GoGuiActions File: `net\sf\gogui\gogui\GoGuiActions.java` ```java=183 public final GuiAction m_actionOOGomoku = new GuiAction(i18n("ACT_OOGomoku")) { public void actionPerformed(ActionEvent e) { m_goGui.actionSetGame(new OOGomoku(m_goGui.getGame().getSize()), new DrawGoBoard(m_goGui.getGuiBoard().getBoardPainter().m_boardStatus, new double[]{1, 0}, new double[]{ 0, 1}), new GoFieldFactory(), 15); } }; public final GuiAction m_actionGomoku = new GuiAction(i18n("ACT_Gomoku")) { public void actionPerformed(ActionEvent e) { m_goGui.actionSetGame(new Gomoku(m_goGui.getGame().getSize()), new DrawGoBoard(m_goGui.getGuiBoard().getBoardPainter().m_boardStatus, new double[]{1, 0}, new double[]{ 0, 1}), new GoFieldFactory(), 15); } }; public final GuiAction m_actionGo = new GuiAction(i18n("ACT_Go")) { public void actionPerformed(ActionEvent e) { m_goGui.actionSetGame(new Go(m_goGui.getGame().getSize()), new DrawGoBoard(m_goGui.getGuiBoard().getBoardPainter().m_boardStatus, new double[]{1, 0}, new double[]{ 0, 1}), new GoFieldFactory(), 19); } }; public final GuiAction m_actionHex = new GuiAction(i18n("ACT_Hex")) { public void actionPerformed(ActionEvent e) { m_goGui.actionSetGame(new Hex(m_goGui.getGame().getSize()), new DrawHexBoard(m_goGui.getGuiBoard().getBoardPainter().m_boardStatus, new double[]{1, 0}, new double[]{ 0.5, Math.sqrt(3)/2}), new HexFieldFactory(), 11); } }; // <TODO> ``` When the game option is clicked, the first step is to define the action to be performed by **m_actionHex**. 1. Construct a GuiAction type action, with the input being the name on the option. For naming conventions, please refer to [i18n](https://openhome.cc/Gossip/Rails/i18n.html). 2. Implement the constructor for this **GuiAction** directly. 3. The main purpose of this constructor is to use **actionSetGame** `net\sf\gogui\gogui\GoGui.java` to set a new game, requiring the input of the game rules with **Hex**(), the game board with **DrawHexBoard**(), the field property with **HexFieldFactory**(), and the default number of grid spaces on the board. ## #### Step 3 Define [i18n](https://openhome.cc/Gossip/Rails/i18n.html) naming format: [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7#diff-2d926892881a2d55ded2c0fad98307712fa69f74b1095f6a5c91825e09c4001a) - text.properties File: `net\sf\gogui\gogui\text.properties` ```java=26 ACT_OOGomoku=&OOGomoku ACT_Gomoku=&Gomoku ACT_Go=&Go ACT_Hex=&Hex // <TODO> ``` <a id="step3"></a> ## ### Add a new game board (optional) #### Step 1: Draw a new board : [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7#diff-4d8b34c3cbe0f060119735e9e5299e797522db4ca817fd8c8b12ed403fe4333c) #### If you want to draw a new chessboard, different from Go or Hex game boards, e.g., chess or Chinese chess boards, you need to implement the following: #### create new class (Draw{***gameName***}Board e.g. DrawHexBoard) for drawing different board (optional) File: `net\sf\gogui\boardpainter` ![](https://i.imgur.com/gQdlKNS.png) #### To draw a new game board, there are two methods: mesh-style drawing and custom irregular drawing. For a more regular grid-like game board, inherit the pre-written **DrawMeshBoard**.java. For a more irregular game board, inherit **DrawBoard** only. 1. Custom drawing: ```java=12 public abstract class DrawBoard { public DrawBoard(BoardStatus boardStatus) { m_boardStatus = boardStatus; } public BoardStatus m_boardStatus; public abstract void draw(Graphics graphics); public abstract GoPoint getPoint(Point point); public abstract Point getLocation(int x, int y); public int[] getScreenRatio() { return new int[]{1, 1}; } public int getFieldSize(double borderSize) { return Math.round((float)Math.floor(m_boardStatus.getWidth() / (m_boardStatus.getSize() + 2 * borderSize))); } public int getFieldOffset() { return (m_boardStatus.getWidth() - m_boardStatus.getSize() * m_boardStatus.getFieldSize()) / 2; } } ``` You need to implement the following functions: - **draw**(): Draw the board. - **getPoint**(): Convert window pixel coordinates to chessboard coordinates (used by external programs that read mouse coordinates). - **getLocation**(): Convert chessboard coordinates to window pixel coordinates (used frequently in `net\sf\gogui\boardpainter\BoardPainter.java`). - **getScreenRatio**(): Chessboard window ratio; If it's a square chessboard, you don't need to implement this function as it defaults to 1:1. If it's a rectangular board, you need to set the ratio, e.g., 16:9 or 70:45 or ... - **getFieldSize**(): Set the size of a square on the chessboard. (window size / board size) - **getFieldOffset**(): Set the size of the chessboard boundary. 2. Mesh-style drawing: ![](https://i.imgur.com/KscsEzg.png) - In **DrawMeshBoard**, the **getPoint**() and **getLocation**() parts have already been implemented. - If you want to draw a mesh-style chessboard, such as a Hex board, you only need to implement the **draw**(), **getScreenRatio**(), **getFieldSize**(), and **getFieldSize**() functions. - When implementing the mesh-style drawing method, you need to provide two direction vectors {horizontal}, {vertical} during construction, such as in a Go board: {1, 0}, {0, 1}, and a Hex board: {1, 0}, {0.5, sqrt(3)/2}. **P.S.** Remember to convert v1 and v2 into unit vectors. When drawing the game board, some board parameters can be obtained using BoardStatus: ```java=11 public class BoardStatus { public BoardStatus(BoardPainter boardpainter) { m_painter = boardpainter; } public int getFieldSize() { return m_painter.m_fieldSize; } public int getFieldOffset() { return m_painter.m_fieldOffset; } public boolean getFlipVertical() { return m_painter.m_flipVertical; } public boolean getFlipHorizontal() { return m_painter.m_flipHorizontal; } public int getSize() { return m_painter.m_size; } public int getWidth() { return m_painter.m_width; } public Point getCenter(int x, int y) { return m_painter.getCenter(x, y); } public BoardConstants getConstants() { return m_painter.m_constants; } private BoardPainter m_painter; } ``` - **getFlipVertical**(): It will return a boolean value, if true, it'll vertically flip the chessboard coordinates. - **getFlipHorizontal**(): It will return a boolean value, if true, it'll horizontally flip the chessboard coordinates. - **getSize**(): Number of chessboard squares. - **getWidth**(): Width of the chessboard. - **getCenter**(): Convert chessboard coordinates to window pixel coordinates. - **getConstants**(): Other chessboard parameters. ## #### Step 2 Add new grid shape (optional): [commit](https://github.com/iis-rlg-lab/gogui-general/commit/9efdd1103dca85fafbdfa6eb3a8bade39bd088b7#diff-7c6ffce60eefe91c3b2aa608a4b6542ad18957d59d286ac249ea78ac4b717e59) - When drawing the grid for the chessboard, you can use pre-drawn shape objects, such as **DrawHexagonGrid**, **DrawCrossGrid**, **DrawSquareGrid**, etc. - During construction, input the grid color and thickness. When drawing the grid, input the coordinates and fieldSize. - If you need to define your own shape, you can create a new class that inherits from **DrawBasicGrid**. File: `net\sf\gogui\boardpainter\DrawHexagonGrid.java` ```java=12 public class DrawHexagonGrid extends DrawBasicGrid { public DrawHexagonGrid(Color color, int thicknese) { super(color, thicknese); } public void draw(Graphics g, double x, double y, int fieldSize) { int[] xPoints = new int[6]; int[] yPoints = new int[6]; int size = (int)(fieldSize/ Math.sqrt(3)); for (int i = 0; i < 6; i++) { double angle = 2 * Math.PI / 6 * (i + 0.5); xPoints[i] = (int) (x + size * Math.cos(angle)); yPoints[i] = (int) (y + size * Math.sin(angle)); } Graphics2D g2 = (Graphics2D)g; g2.setColor(getColor()); g2.setStroke(new BasicStroke(getThicknese())); g2.drawPolygon(xPoints, yPoints, 6); } } ``` <a id="step4"></a> ## ### Add an interface for drawing different shape of field background (optional) #### Step 1: Add an interface for drawing different shape of field background: [commit](https://github.com/iis-rlg-lab/gogui-general/commit/31776b800a93534d32304d49e02aaa5523b1e2de#diff-c3efe99082e9d931508103867ef2eb647e7beca40f3bfae9ef6396d92828e081) File: `net\sf\gogui\boardpainter\HexFieldFactory.java` ![](https://hackmd.io/_uploads/S1AvEbWLn.png) ![](https://hackmd.io/_uploads/HJ0sVlbL2.png) #### In this part, we will implement the visualization functionality used in the analysis tool. Since different chessboards require displaying different background shapes, we are using the "Factory" object-oriented design pattern here. ```java=2 package net.sf.gogui.boardpainter; /** * * */ public interface FieldFactory { public Field createField(); } ``` - In this step, we only need to implement the **createField**() function. You can refer to the following Hex examples. ```java=2 package net.sf.gogui.boardpainter; /** * * @author tylerliu */ public class HexFieldFactory implements FieldFactory { public Field createField() { return new Field(new DrawHexProperty()); } } ``` - Here we inherit the FieldFactory interface, which can create a field object with the desired field property. ## #### Step 2: Implement the shape of field background [commit](https://github.com/iis-rlg-lab/gogui-general/commit/31776b800a93534d32304d49e02aaa5523b1e2de#diff-dfc7c1a8cd5432b9c2eaf24b3d098bb5433caa340db2200932a72fd0325ac6fe) File: `net\sf\gogui\boardpainter\DrawHexProperty.java` ```java=11 public abstract class DrawProperty { public abstract void fillBackGround(Graphics graphics, int width, int height, Color color); public double getHeightRatio() { return 1.0; } public double getWidthRatio() { return 1.0; } } ``` #### We inherit the DrawProperty interface for the Property part, allowing developers to use this interface to implement customized Field properties. - **fillBackGround**(): This function is similar to the "draw grid" part in the previous steps. You can refer to the following example. ```java=11 public class DrawHexProperty extends DrawProperty { public void fillBackGround(Graphics graphics, int width, int height, Color color) { int x = width / 2; int y = height / 2; int[] xPoints = new int[6]; int[] yPoints = new int[6]; int xsize = (int)(width / Math.sqrt(3)); int ysize = (int)(width / Math.sqrt(3)); for (int i = 0; i < 6; i++) { double angle = 2 * Math.PI / 6 * (i + 0.5); xPoints[i] = (int) (x + xsize * Math.cos(angle)); yPoints[i] = (int) (y + ysize * Math.sin(angle)); } graphics.setColor(color); graphics.fillPolygon(xPoints, yPoints, 6); } public double getHeightRatio() { return 2 * Math.sqrt(3) / 3; } } ``` ## General Gogui 開發紀錄 #### 2023/5/18 - 新增畫不同field背景的interface。 #### 2023/5/3 - 新增OOGomku遊戲規則。 - 新增Hex遊戲規則與Hex棋盤。 - 改寫boardPainter,讓畫棋盤可以更彈性。 #### 2023/3/27 - 抽出Go遊戲規則。 - 使用java abstract class模擬GoTextProtocol功能,讓後續新增遊戲可直接寫在gogui,不必額外載入外掛程式。 - 新增Gomoku遊戲規則。 # <style> input[type=checkbox] { position: relative; } input[type=checkbox]:hover { box-shadow: black 1px 1px 5px 3px; transition: 0.2s; } input[type=checkbox]:checked:after { content: '✔️'; position: absolute; transform: translate(-5px, -5px); background: white; } input[type=checkbox]:not(:checked):after { content: '❌'; position: absolute; transform: translate(-5px, -5px); background: white; } </style> # GoGui quick guide ## DOC Move to the child node which has the largest subtree. ![](https://i.imgur.com/kp95Z3R.png) Find with an extended regular expression in the comments. Example: \`regexp1\` & (!\`regexp2\` | \`regexp3\`) ![](https://i.imgur.com/IKU4u5R.png) ## 介面 [GoGui 原始碼](https://github.com/Remi-Coulom/gogui/releases) ![](https://i.imgur.com/jmPzZGE.png) <a id="quick-guide"></a> ## 架構簡介 File: `gogui\GoGui.java` ```java=142 public class GoGui extends JFrame implements AnalyzeDialog.Listener, GuiBoard.Listener, GameTreeViewer.Listener, GtpShell.Listener, ScoreDialog.Listener, GoGuiMenuBar.Listener, ContextMenu.Listener, LiveGfx.Listener ``` `GoGui` 是最核心的類別,他繼承了許多不同的事件,因此可以說是彙總所有功能的地方。從架構層面來說,這個類別扮演了解耦合中的重要角色,整個程式可以說是用 event driven 模式去設計的。 ## 功能與原始碼 ### 快速工具列 ![](https://i.imgur.com/B4HAVwy.png) File: `gogui\GoGuiToolBar.java` ```java=21 public GoGuiToolBar(GoGui goGui) { m_goGui = goGui; GoGuiActions actions = m_goGui.getActions(); m_actions = actions; addButton(actions.m_actionOpen); addButton(actions.m_actionSave); addButton(actions.m_actionNewGame); addSeparator(); addToggleButton(actions.m_actionSetupBlack); addToggleButton(actions.m_actionSetupWhite); addButton(actions.m_actionComputerNone); addSeparator(); addButton(actions.m_actionPass); addButton(actions.m_actionPlay); addSeparator(); addButton(actions.m_actionBeginning); addButton(actions.m_actionBackwardTen); addButton(actions.m_actionBackward); addButton(actions.m_actionForward); addButton(actions.m_actionForwardTen); addButton(actions.m_actionEnd); addSeparator(); addButton(actions.m_actionNextVariation); addButton(actions.m_actionPreviousVariation); addSeparator(); addButton(actions.m_actionForwardMaxNode); addSeparator(); addButton(actions.m_actionSearchPattern); add(m_goGui.getPatternTextField()); if (! Platform.isMac()) setRollover(true); setFloatable(false); } ``` 在 `GoGuiToolBar` 的建構子中可以新增工具列中的功能,然後每個功能都有對應的 `GuiAction` File: `gogui/GoGuiActions.java` ```java=237 public final GuiAction m_actionForward = new GuiAction(i18n("ACT_FORWARD"), i18n("TT_FORWARD"), KeyEvent.VK_RIGHT, "gogui-next") { public void actionPerformed(ActionEvent e) { m_goGui.actionForward(1); } }; ``` `GuiAction` 中可以定義按鈕上的 `icon`,還有對應的 callback ### 工具列 ![](https://i.imgur.com/2FeYYx2.png) 於 `gogui/GoGuiMenuBar.java` 創建工具列,並且呼叫定義於 `gogui/GoGuiActions.java` 的對應名稱與動作 ex: 棋盤大小設定選單 File: `gogui/GoGuiMenuBar.java` ```java private GuiMenu createBoardSizeMenu(GoGuiActions actions) { m_menuBoardSize = new GuiMenu(i18n("MEN_BOARDSIZE")); ButtonGroup group = new ButtonGroup(); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSize9); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSize11); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSize13); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSize15); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSize17); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSize19); m_menuBoardSize.addRadioItem(group, actions.m_actionBoardSizeOther); return m_menuBoardSize; } ``` File: `gogui/GoGuiActions.java` ```java public final GuiAction m_actionBoardSize9 = new GuiAction(i18n("ACT_BOARDSIZE_9")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSize(9); } }; public final GuiAction m_actionBoardSize11 = new GuiAction(i18n("ACT_BOARDSIZE_11")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSize(11); } }; public final GuiAction m_actionBoardSize13 = new GuiAction(i18n("ACT_BOARDSIZE_13")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSize(13); } }; public final GuiAction m_actionBoardSize15 = new GuiAction(i18n("ACT_BOARDSIZE_15")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSize(15); } }; public final GuiAction m_actionBoardSize17 = new GuiAction(i18n("ACT_BOARDSIZE_17")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSize(17); } }; public final GuiAction m_actionBoardSize19 = new GuiAction(i18n("ACT_BOARDSIZE_19")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSize(19); } }; public final GuiAction m_actionBoardSizeOther = new GuiAction(i18n("ACT_BOARDSIZE_OTHER")) { public void actionPerformed(ActionEvent e) { m_goGui.actionBoardSizeOther(); } }; ``` ### 搜尋功能 File: `gui\FindDialog.java` File: `gogui\GoGui.java` ```java=752 public void actionFind() { if (! checkStateChangePossible()) return; Pattern pattern = FindDialog.run(this, m_comment.getSelectedText(), m_messageDialogs); if (pattern == null) return; m_pattern = pattern; if (NodeUtil.commentContains(getCurrentNode(), m_pattern)) m_comment.markAll(m_pattern); else actionFindNext(); } public void actionFindNext() { if (! checkStateChangePossible()) return; if (m_pattern == null) return; protectGui(); showStatus(i18n("STAT_FIND_SEARCHING_COMMENTS")); Runnable runnable = new Runnable() { public void run() { try { ConstNode root = getTree().getRootConst(); ConstNode currentNode = getCurrentNode(); ConstNode node = NodeUtil.findInComments(currentNode, m_pattern); boolean cancel = false; if (node == null && getCurrentNode() != root) { unprotectGui(); if (showQuestion(i18n("MSG_FIND_CONTINUE"), i18n("MSG_FIND_CONTINUE_2"), i18n("LB_FIND_CONTINUE"), false)) { protectGui(); node = root; if (! NodeUtil.commentContains(node, m_pattern)) node = NodeUtil.findInComments(node, m_pattern); } else cancel = true; } if (! cancel) { if (node == null) { unprotectGui(); showInfo(i18n("MSG_FIND_NOT_FOUND"), format(i18n("MSG_FIND_NOT_FOUND_2"), m_pattern), false); m_pattern = null; } else { gotoNode(node); boardChangedBegin(false, false); m_comment.markAll(m_pattern); } } } finally { unprotectGui(); clearStatus(); } } }; SwingUtilities.invokeLater(runnable); } ``` ### 繪製棋盤 `boardpainter\BoardPainter.java` File: `gogui\GoGui.java` 每次盤面改變都會被呼叫的函數,如果需要畫新的東西可以加在這裡面 ```java=4962 private void updateGuiBoard() { if (m_showVariations == ShowVariations.CHILDREN) { ConstPointList moves = NodeUtil.getChildrenMoves(getCurrentNode()); GuiBoardUtil.showMoves(m_guiBoard, moves); } else if (m_showVariations == ShowVariations.SIBLINGS && NodeUtil.hasSiblingMoves(getCurrentNode())) { ConstNode father = getCurrentNode().getFatherConst(); if (father != null) { ConstPointList moves = NodeUtil.getChildrenMoves(father); if (moves.size() > 1) GuiBoardUtil.showMoves(m_guiBoard, moves); } } GuiBoardUtil.showMarkup(m_guiBoard, getCurrentNode()); } ``` ### 滑鼠事件 File: `gui\GuiBoard.java` ```java=304 m_panel.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { GoPoint point = m_panel.getPoint(e); if (point == null) return; // mousePressed and mouseReleased (platform dependency) if (e.isPopupTrigger()) { contextMenu(point); return; } int button = e.getButton(); int count = e.getClickCount(); if (button != MouseEvent.BUTTON1) return; if (count == 2) fieldClicked(point, true); else { int modifiers = e.getModifiers(); int mask = (ActionEvent.CTRL_MASK | ActionEvent.ALT_MASK | ActionEvent.META_MASK); boolean modifiedSelect = ((modifiers & mask) != 0); fieldClicked(point, modifiedSelect); } } public void mouseReleased(MouseEvent e) { GoPoint point = m_panel.getPoint(e); if (point == null) return; if (e.isPopupTrigger()) { contextMenu(point); return; } } }); ``` ### 遊戲規則 ### 紀錄 ### MenuBar 事件傳遞方式 底下使用 New Program 作為範例,詳解了按下此按鈕後程式是如何執行的 ![](https://i.imgur.com/FZCjuZa.png) File: `gogui\GoGuiMenuBar.java` ```java=511 menu.add(actions.m_actionNewProgram); ``` File: `gogui\GoGuiActions.java` ```java=398 public final GuiAction m_actionNewProgram = new GuiAction(i18n("ACT_NEW_PROGRAM")) { public void actionPerformed(ActionEvent e) { // 當事件(New Program 被按下)發生時,執行 actionNewProgram() m_goGui.actionNewProgram(); } }; ``` File: `gogui\GoGui.java` ```java=1216 public void actionNewProgram() { m_newProgram = new Program("", "", "", "", ""); final ProgramEditor editor = new ProgramEditor(); m_newProgram = editor.editItem(this, i18n("TIT_NEW_PROGRAM"), m_newProgram, true, false, m_messageDialogs); if (m_newProgram == null) return; protectGui(); SwingUtilities.invokeLater(new Runnable() { public void run() { attachNewProgram(m_newProgram.m_command, m_newProgram); if (! isGtpCompatibleWithGame()) if (isGtpRuler()) try { initGameRuler(m_newProgram.m_command, m_newProgram.m_workingDirectory, m_newProgram.m_name); } catch (ExecFailed e) { } else if (isRulerAttached()) actionDetachRuler(false); unprotectGui(); if (m_gtp == null || m_gtp.isProgramDead()) { m_newProgram = editor.editItem(GoGui.this, i18n("TIT_NEW_PROGRAM"), m_newProgram, true, false, m_messageDialogs); if (m_newProgram == null) return; SwingUtilities.invokeLater(this); return; } m_newProgram.m_name = m_gtp.getLabel(); m_newProgram.m_version = m_version; m_newProgram.setUniqueLabel(m_programs); m_newProgram = editor.editItem(GoGui.this, i18n("TIT_NEW_PROGRAM"), m_newProgram, false, true, m_messageDialogs); if (m_newProgram == null) { actionDetachProgram(); return; } m_programs.add(m_newProgram); m_program = m_newProgram; m_prefs.putInt("program", m_programs.size() - 1); m_menuBar.setPrograms(m_programs, false); // 底下有接著解釋這個函數 Program.save(m_programs, false); updateViews(false); } }); } ``` 1265 行可以動態調整 menu bar 的內容 (新增/刪除) File: `gogui\GoGuiMenuBar.java` ![](https://i.imgur.com/lCSGVau.png) ```java=131 public void setPrograms(ArrayList<Program> programs, boolean ruler) { if (ruler) // 清空 Attach 下拉選單中的物件 { m_menuRuler.setEnabled(! programs.isEmpty()); ArrayList<JMenuItem> items = m_rulerItems; for (int i = 0; i < items.size(); ++i) m_menuRuler.remove(items.get(i)); } else { m_menuAttach.setEnabled(! programs.isEmpty()); ArrayList<JMenuItem> items = m_programItems; for (int i = 0; i < items.size(); ++i) m_menuAttach.remove(items.get(i)); } if (programs.isEmpty()) return; for (int i = 0; i < programs.size(); ++i) // 將 programs 新增至 Attach 下拉選單 { Program program = programs.get(i); String[] mnemonicArray = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; String text; String mnemonic; if (! Platform.isMac() && i < mnemonicArray.length) { mnemonic = mnemonicArray[i]; text = mnemonic + ": " + program.m_label; } else { mnemonic = ""; text = program.m_label; } JMenuItem item = new JMenuItem(text); if (! mnemonic.equals("")) { KeyStroke keyStroke = KeyStroke.getKeyStroke(mnemonic); int code = keyStroke.getKeyCode(); item.setMnemonic(code); } final int index = i; item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (ruler) // 事件發生時,將事件傳遞給外部,底下有解釋如何傳遞 m_listener.actionAttachRuler(index); else m_listener.actionAttachProgram(index); } }); StringBuilder toolTip = new StringBuilder(128); if (program.m_name != null) toolTip.append(program.m_name); if (program.m_version != null && ! program.m_version.equals("") && program.m_version.length() < 40) { toolTip.append(' '); toolTip.append(program.m_version); } if (program.m_command != null) { toolTip.append(" ("); toolTip.append(program.m_command); toolTip.append(')'); } item.setToolTipText(toolTip.toString()); if (ruler) { m_menuRuler.add(item); m_rulerItems.add(item); } else { m_menuAttach.add(item); m_programItems.add(item); } } } ``` :::info 這段 Java 程式碼是用來設定一個視窗應用程式的選單。代碼會檢查給定的參數 ruler,然後依照參數的不同來選擇要處理的選單(m_menuRuler 或 m_menuAttach),並將其狀態設為是否可用(可用狀態取決於 programs 是否為空)。然後,代碼將選單中的所有項目移除,再根據 programs 的長度創建新的選單項目。每個選單項目都會設置快速鍵、提示信息,並為其添加一個操作事件侦听器(ActionListener),以在用戶點擊該項目時執行特定的操作。 —ChatGPT ::: `GoGuiMenuBar` 透過此抽象類別(Listener)將事件送至外部,最後會由 `GoGui` 這個類別去監聽事件 ```java=27 /** Listener to events that are not handled by GoGuiActions. */ public interface Listener { void actionGotoBookmark(int i); void actionAttachProgram(int i); void actionAttachRuler(int i); } ``` File: `gogui\GoGui.java` 這三個函數就是上面的 `Listener` 在 `GoGui` 這個類別中的實作 ```java=376 public void actionAttachProgram(int index) { m_prefs.putInt("program", index); // 將正在使用的 programs 寫入到檔案中(猜的) actionAttachProgram(m_programs.get(index)); } public void actionAttachRuler(int index) { Program ruler = m_rulers.get(index); try { initGameRuler(ruler.m_command, ruler.m_workingDirectory, ruler.m_label); } catch (ExecFailed e) { } if (m_gameRuler != null) { m_prefs.putInt("ruler", index); } } public void actionAttachProgram(final Program program) { if (! checkCommandInProgress()) return; protectGui(); Runnable runnable = new Runnable() { public void run() { try { attachNewProgram(program.m_command, program); } finally { unprotectGui(); } } }; SwingUtilities.invokeLater(runnable); } ``` [TODO](#TODO-以Hex-game為例) ## 開發紀錄 #### 2023/3/27 - 抽出Go遊戲規則。 - 使用java abstract class模擬GoTextProtocol功能,讓後續新增遊戲可直接寫在gogui,不必額外載入外掛程式。 - 新增Gomoku遊戲規則。 # 目標: 更 general 版本的 GoGui,能夠自訂棋盤形狀,關閉某些格子,自訂棋子圖片,等等。 #### 2022/12/12 - 寫了 `boardconfig\BoardShape.java`,能夠自動讀取相同目錄下的 `config` 資料夾內的 `.shapeconfig` 檔。 - 在 menu 的 `Game` 裡面新增了 `Board Shape` 選項。 TODO: - 還需要研究如何接上 Action 讓 `Board Shape` 能跟 `ShapeConfig` 對應起來。 - 研究如何動態設定 `Board Shape` 內的選項數量,目前猜測是使用類似 `gogui/GoGuiMenuBar.java` 中的 `setPrograms` 函數的作法即可。 #### 2022/12/24 TODO: 從 `gui\GuiBoard.java` 304 行的 mouse event 中增加新功能,於 `Listener` 中加入新的函數,用於將滑鼠的點擊、拖曳、釋放等事件轉送到 `gogui\GoGui.java` 中,如下 ```java=42 /** Callback for clicks on a field. */ public interface Listener { /** Callback for click on a field. This callback is triggered with mouse clicks or the enter key if the cursor is shown. @param p The point clicked. @param modifiedSelect Modified select. True if the click was a double click or with the right mouse button or if a modifier key (Ctrl, Alt, Meta) was pressed while clicking, as long as it was not a (platform-dependent) popup menu trigger. */ void fieldClicked(GoPoint p, boolean modifiedSelect); /** Callback for context menu. This callback is triggered with mouse clicks that trigger popup menus (platform-dependent). @param point The point clicked. @param invoker The awt.Component that was clicked on. @param x The x coordinate on the invoker component. @param y The y coordinate on the invoker component. */ void contextMenu(GoPoint point, Component invoker, int x, int y); } ``` 然後 `GoGui` 收到事件後就去修改成員 `boardconfig\BoardShape.java` 的物件,使棋盤的產生型變等效果,每次修改 `BoardShape` 時都要要求 `boardpainter\BoardPainter.java` 重新繪製棋盤,這樣就可以實現滑鼠編輯棋盤的功能,可能還需要加入 `save` 等按鈕,在編輯完成後會寫入到 `.shapeconfig` 檔中,方便以後使用此次編輯的結果。 實作細節: 在 `GoGui` 裡面新增以下成員 ```java private BoardShape m_boardShape private ArrayList<BoardShape> m_boardShapes; ``` `m_boardShape` 用於紀錄正在使用的棋盤,而 `m_boardShapes` 則是記錄所有可用的棋盤。 接著要在 `boardpainter\BoardPainter.java` 中根據 `BoardShape` 繪製棋盤(要怎麼把 `m_boardShape` 傳給 `BoardPainter` 還有待研究),`BoardShape` 中可能會有一個 `boolean` 值,用於紀錄現在是否正在使用編輯模式,可能會需要畫出一些輔助線,讓使用者方便調整棋盤。 透過在 `gogui\GoGuiMenuBar.java` 新增按鈕來(開啟/關閉)棋盤編輯功能,還有新增棋盤等等操作,當使用者新增了棋盤,就要將新棋盤加入到 `GoGuiMenuBar` 中(新增方法參考 <a href="#%E5%B7%A5%E5%85%B7%E5%88%97">工具列</a>)。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully