# GoGui Note
## TODO: Add Games
- Bugs
- [ ] 按 clear board 後視窗會跳走
- [x] Hex 顏色錯誤 (需要將棋盤轉 90°)
- [x] 位置判定錯誤 (按紅點會下在 A)

- [ ] 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] 預設遊戲沒打勾

- [x] 用棋譜開 GoGui 時棋譜會被洗掉
- [ ] Hex new game 大小會變大

- [ ] 六角形間有縫隙

- 打開程式時預設遊戲為上次關閉時的遊戲
- 加遊戲
- 現有:
- 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.



4. Find the file that we just clone from github.


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`

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";
}
```


- **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.

```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`

#### 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:

- 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`


#### 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.

Find with an extended regular expression in the comments.
Example: \`regexp1\` & (!\`regexp2\` | \`regexp3\`)

## 介面
[GoGui 原始碼](https://github.com/Remi-Coulom/gogui/releases)

<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 模式去設計的。
## 功能與原始碼
### 快速工具列

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
### 工具列

於 `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 作為範例,詳解了按下此按鈕後程式是如何執行的

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`

```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>)。