mirror of
https://github.com/tiennm99/caro.git
synced 2026-09-10 06:20:07 +00:00
feat(gomoku): init
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(rg:*)",
|
||||
"Bash(grep:*)"
|
||||
"Bash(grep:*)",
|
||||
"Bash(mv:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(java:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# Gomoku Conversion Summary
|
||||
|
||||
## Overview
|
||||
Successfully converted the Landlords card game server-client project to a Gomoku (Five-in-a-Row) game while preserving the underlying Netty-based architecture and event-driven communication system.
|
||||
|
||||
## Key Changes Made
|
||||
|
||||
### 1. Enums Updated for Gomoku
|
||||
|
||||
**ServerEventCode.java**
|
||||
- Replaced landlord-specific events with Gomoku events
|
||||
- Added: `CODE_GAME_MOVE`, `CODE_GAME_RESET`
|
||||
- Removed: `CODE_GAME_LANDLORD_ELECT`, `CODE_GAME_POKER_PLAY*`
|
||||
|
||||
**ClientEventCode.java**
|
||||
- Updated with Gomoku-specific client events
|
||||
- Added: `CODE_GAME_MOVE_SUCCESS`, `CODE_GAME_MOVE_INVALID`, `CODE_GAME_WIN`, `CODE_GAME_LOSE`, `CODE_GAME_DRAW`
|
||||
- Replaced: `CODE_SHOW_POKERS` → `CODE_SHOW_BOARD`
|
||||
|
||||
**SellType.java → PieceType.java**
|
||||
- Complete replacement for Gomoku pieces
|
||||
- Enum values: `EMPTY`, `BLACK`, `WHITE`
|
||||
|
||||
**New GameResult.java**
|
||||
- Enum for game outcomes: `BLACK_WIN`, `WHITE_WIN`, `DRAW`, `IN_PROGRESS`
|
||||
|
||||
**ClientRole.java**
|
||||
- Added: `BLACK_PLAYER`, `WHITE_PLAYER`
|
||||
|
||||
### 2. New Game Entities
|
||||
|
||||
**Board.java**
|
||||
- 15x15 game board implementation
|
||||
- Win detection for 5-in-a-row (horizontal, vertical, diagonal)
|
||||
- Move validation and game state management
|
||||
|
||||
**GameMove.java**
|
||||
- Represents a single move with position, piece type, and player ID
|
||||
- Timestamp tracking for move history
|
||||
|
||||
**Room.java Updates**
|
||||
- Removed landlord/poker-specific fields
|
||||
- Added: `gameBoard`, `blackPlayerId`, `whitePlayerId`, `currentTurn`, `moveHistory`
|
||||
- Added game management methods: `switchTurn()`, `resetGame()`, `isPlayerTurn()`
|
||||
|
||||
**ClientSide.java Updates**
|
||||
- Removed poker-specific fields (`pokers`)
|
||||
- Simplified for Gomoku gameplay
|
||||
|
||||
### 3. Game Logic Implementation
|
||||
|
||||
**GomokuHelper.java**
|
||||
- Core game logic utilities
|
||||
- Move validation and execution
|
||||
- Board display formatting
|
||||
- Move history formatting
|
||||
- Win condition checking
|
||||
|
||||
**GomokuAI.java**
|
||||
- Three difficulty levels (Easy, Medium, Hard)
|
||||
- Strategic move evaluation
|
||||
- Threat detection and blocking
|
||||
- Position scoring system
|
||||
|
||||
### 4. Documentation Updates
|
||||
|
||||
**README.md**
|
||||
- Updated title and description for Gomoku
|
||||
- New gameplay rules and commands
|
||||
- Board position format (row,col)
|
||||
- Game commands: board, history, reset, pass, exit
|
||||
|
||||
**PROTOCO_CN.md**
|
||||
- Updated from Landlords to Gomoku terminology
|
||||
- Protocol documentation updated for game moves
|
||||
|
||||
**messages_zh_CN.properties**
|
||||
- Added Gomoku-specific messages
|
||||
- Move validation error messages
|
||||
- Game result messages
|
||||
|
||||
### 5. Testing
|
||||
|
||||
**GomokuHelperTest.java**
|
||||
- Comprehensive unit tests for game logic
|
||||
- Tests for win conditions, move validation, turn management
|
||||
- Board formatting and game reset functionality
|
||||
|
||||
## Architecture Preserved
|
||||
|
||||
### Network Layer
|
||||
- Netty 4.x server-client communication maintained
|
||||
- Protobuf serialization for data transfer
|
||||
- TCP and WebSocket protocol support
|
||||
|
||||
### Event-Driven System
|
||||
- Event listener pattern preserved
|
||||
- Client-server event codes updated for Gomoku
|
||||
- Reactive architecture maintained
|
||||
|
||||
### Room Management
|
||||
- Multi-room support
|
||||
- Spectator functionality
|
||||
- PvP and PvE modes
|
||||
|
||||
## Game Features
|
||||
|
||||
### Core Gameplay
|
||||
- 15x15 board with standard Gomoku rules
|
||||
- Turn-based gameplay (Black moves first)
|
||||
- Win condition: 5 pieces in a row
|
||||
- Real-time move validation
|
||||
|
||||
### AI Support
|
||||
- Three difficulty levels
|
||||
- Strategic positioning algorithms
|
||||
- Threat detection and blocking
|
||||
|
||||
### User Experience
|
||||
- Command-line interface preserved
|
||||
- Board visualization
|
||||
- Move history tracking
|
||||
- Real-time game state updates
|
||||
|
||||
### Multiplayer Features
|
||||
- Room-based gameplay
|
||||
- Spectator mode
|
||||
- Player rankings and statistics
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Game Board
|
||||
- 2D array representation (15x15)
|
||||
- Efficient win detection algorithms
|
||||
- Move validation with bounds checking
|
||||
|
||||
### Turn Management
|
||||
- Player role assignment (Black/White)
|
||||
- Turn switching logic
|
||||
- Game state tracking
|
||||
|
||||
### Communication Protocol
|
||||
- Event-based messaging
|
||||
- JSON data structures for game state
|
||||
- Protobuf serialization for network efficiency
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
- Advanced AI algorithms (minimax with alpha-beta pruning)
|
||||
- Tournament mode support
|
||||
- Replay system
|
||||
- Enhanced ranking system
|
||||
- Move undo functionality
|
||||
- Time limits per move
|
||||
|
||||
### Technical Improvements
|
||||
- Performance optimizations for large-scale deployment
|
||||
- Database integration for persistent game history
|
||||
- Web-based client interface
|
||||
- Mobile app support
|
||||
|
||||
## Conclusion
|
||||
|
||||
The conversion successfully transforms the Landlords game into a fully functional Gomoku game while maintaining the robust networking architecture and event-driven design patterns. The modular structure allows for easy extension and customization, making it suitable for both casual play and competitive gaming scenarios.
|
||||
+12
-12
@@ -1,23 +1,23 @@
|
||||
[toc]
|
||||
# Ratel-Client Development Documentation
|
||||
# Gomoku-Client Development Documentation
|
||||
## Introduction
|
||||
### What is Ratel
|
||||
[Ratel](https://github.com/ainilili/ratel) is a project that allows you to play Landlords (Dou Di Zhu) in the command line. You can use a compact jar package to play games in terminals with JVM environment, supporting both player vs player and player vs AI modes to enrich your leisure time!
|
||||
### What is Gomoku Game
|
||||
This is a project that allows you to play Gomoku (Five-in-a-Row) in the command line. You can use a compact jar package to play games in terminals with JVM environment, supporting both player vs player and player vs AI modes to enrich your leisure time!
|
||||
|
||||
Ratel is developed using Java language, with [Netty 4.x](https://github.com/netty/netty) network framework combined with [protobuf](https://github.com/protocolbuffers/protobuf) data protocol, supporting multiple clients playing simultaneously.
|
||||
The game is developed using Java language, with [Netty 4.x](https://github.com/netty/netty) network framework combined with [protobuf](https://github.com/protocolbuffers/protobuf) data protocol, supporting multiple clients playing simultaneously.
|
||||
|
||||
### Ratel-Client Extension
|
||||
Ratel is designed with reactive architecture, communicating through event codes. For Ratel-Client, it supports cross-platform extension. In other words, any backend language can develop Ratel-Client!
|
||||
### Gomoku-Client Extension
|
||||
The game is designed with reactive architecture, communicating through event codes. For Gomoku-Client, it supports cross-platform extension. In other words, any backend language can develop Gomoku-Client!
|
||||
|
||||
Before developing Ratel-Client, you should know:
|
||||
- Ratel Server-Client interaction network protocol is ``TCP/IP``.
|
||||
- Ratel Server-Client interaction data protocol is [protobuf](https://github.com/protocolbuffers/protobuf).
|
||||
- Ratel is event-driven, connecting various components in the Client.
|
||||
- All text content in Ratel is displayed by the Client.
|
||||
Before developing Gomoku-Client, you should know:
|
||||
- Gomoku Server-Client interaction network protocol is ``TCP/IP``.
|
||||
- Gomoku Server-Client interaction data protocol is [protobuf](https://github.com/protocolbuffers/protobuf).
|
||||
- Gomoku is event-driven, connecting various components in the Client.
|
||||
- All text content in Gomoku is displayed by the Client.
|
||||
|
||||
## Integration
|
||||
### Architect Your Client
|
||||
We can rewrite the Ratel client using any backend language. The default Ratel client is written in Java. If you want to rewrite it in other languages, here's a recommended architecture:
|
||||
We can rewrite the Gomoku client using any backend language. The default Gomoku client is written in Java. If you want to rewrite it in other languages, here's a recommended architecture:
|
||||

|
||||
|
||||
This architecture is very friendly for event handling. You can design an abstract ``Event-Listener`` interface, then develop different implementations to handle response data for different ``CODE`` values. For example, a simple example - Poker display, here's the pseudo code for our processing flow:
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
/badge.svg?branch=master)
|
||||

|
||||
|
||||
# This project is no longer maintained, please try the [👉 new version](https://github.com/ratel-online/server), which supports joker mode, Texas Hold'em, adds timeout mechanism, and perfectly recreates Happy Landlords, [online experience](http://ratel.isnico.com/)
|
||||
# Gomoku Game Server-Client
|
||||
|
||||
## Introduction
|
||||
Command-line Landlords game based on Netty, born for procrastination and leisure~
|
||||
Command-line Gomoku (Five-in-a-Row) game based on Netty, converted from the original Landlords game~
|
||||
|
||||
## Installation
|
||||
First download and package, ensure maven and JRE environment are installed locally:
|
||||
@@ -31,37 +31,39 @@ java -jar landlords-client/target/landlords-client-#{version}.jar
|
||||
```
|
||||
**Note**, please replace ``#{version}`` with the current running version in actual execution!
|
||||
## Gameplay Introduction
|
||||
Online trial: [Portal](http://ratel.isnico.com)
|
||||
Play Gomoku (Five-in-a-Row) on a 15x15 board!
|
||||
|
||||

|
||||
|
||||
### Card Playing Rules
|
||||
All card types:
|
||||
### Game Rules
|
||||
- Two players take turns placing black and white pieces on a 15x15 board
|
||||
- The first player uses black pieces, the second player uses white pieces
|
||||
- The goal is to be the first to form an unbroken line of five pieces horizontally, vertically, or diagonally
|
||||
- The game ends when one player achieves five in a row or the board is full (draw)
|
||||
|
||||
### Game Commands
|
||||
```
|
||||
┌──┐──┐──┐──┐──┐──┐──┐──┐──┐──┐──┐──┐──┐──┐──┐
|
||||
│3 |4 |5 |6 |7 |8 |9 |10|J |Q |K |A |2 |S |X |
|
||||
│♦ |♦ |♦ |♦ |♦ |♦ |♦ |♦ |♦ |♦ |♦ |♦ |♦ | | |
|
||||
└──┘──┘──┘──┘──┘──┘──┘──┘──┘──┘──┘──┘──┘──┘──┘
|
||||
Board positions are specified as: row,col (e.g., 7,7 for center)
|
||||
```
|
||||
Examples:
|
||||
- King bomb: ``sx``
|
||||
- Straight: ``34567``
|
||||
- Three with one: ``3334``
|
||||
- Airplane: ``333444a2``
|
||||
- Single 10: ``0`` or ``t``
|
||||
- Single A: ``a`` or ``1``
|
||||
- Max straight: ``34567890jqka``
|
||||
- Don't want to play: ``pass`` or ``p``
|
||||
- Make a move: ``7,7`` (place piece at row 7, column 7)
|
||||
- Show board: ``board`` or ``b``
|
||||
- Show move history: ``history`` or ``h``
|
||||
- Pass turn: ``pass`` or ``p``
|
||||
- Exit: ``exit`` or ``e``
|
||||
- [More](https://zh.wikipedia.org/zh-sg/%E9%AC%A5%E5%9C%B0%E4%B8%BB)
|
||||
- Reset game: ``reset`` or ``r`` (room owner only)
|
||||
|
||||
#### Protocol Support
|
||||
- TCP
|
||||
- Websocket
|
||||
|
||||
Websocket protocol address is ``ws://host:port/ratel``, Websocket port needs to be original port plus 1 (if tcp port is 1024, then ws port should be 1025)
|
||||
## Procrastination Club
|
||||
QQ Group ``948365095``, procrastination feels great for a moment, keep procrastinating always feels great!
|
||||
## Game Features
|
||||
- Support for Player vs Player (PVP) mode
|
||||
- Support for Player vs AI (PVE) mode
|
||||
- Real-time spectator mode
|
||||
- Move history tracking
|
||||
- Automatic win detection
|
||||
|
||||
## Ecosystem
|
||||
- [go-ratel-client](https://github.com/ZuoFuhong/go-ratel)
|
||||
@@ -76,7 +78,10 @@ QQ Group ``948365095``, procrastination feels great for a moment, keep procrasti
|
||||
- [Changelog](https://github.com/ainilili/ratel/blob/master/UPDATE.md)
|
||||
|
||||
## Plans
|
||||
- Support for advanced difficulty robots
|
||||
- Support for advanced difficulty AI
|
||||
- Tournament mode support
|
||||
- Ranking system
|
||||
- Replay system
|
||||
|
||||
## More
|
||||
- [Serverlist.json](https://github.com/ainilili/ratel/blob/master/serverlist.json) is the current server list. If your server is deployed with the current latest version of the server and shared with everyone, you can submit it to us through PR!
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.nico.ratel.landlords.entity;
|
||||
|
||||
import org.nico.ratel.landlords.enums.PieceType;
|
||||
import org.nico.ratel.landlords.enums.GameResult;
|
||||
|
||||
public class Board {
|
||||
|
||||
public static final int BOARD_SIZE = 15;
|
||||
public static final int WIN_CONDITION = 5;
|
||||
|
||||
private PieceType[][] board;
|
||||
private int moveCount;
|
||||
private GameResult result;
|
||||
|
||||
public Board() {
|
||||
this.board = new PieceType[BOARD_SIZE][BOARD_SIZE];
|
||||
this.moveCount = 0;
|
||||
this.result = GameResult.IN_PROGRESS;
|
||||
initializeBoard();
|
||||
}
|
||||
|
||||
private void initializeBoard() {
|
||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
||||
for (int j = 0; j < BOARD_SIZE; j++) {
|
||||
board[i][j] = PieceType.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValidMove(int row, int col) {
|
||||
return row >= 0 && row < BOARD_SIZE &&
|
||||
col >= 0 && col < BOARD_SIZE &&
|
||||
board[row][col] == PieceType.EMPTY;
|
||||
}
|
||||
|
||||
public boolean makeMove(int row, int col, PieceType piece) {
|
||||
if (!isValidMove(row, col)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
board[row][col] = piece;
|
||||
moveCount++;
|
||||
|
||||
// Check for win condition
|
||||
if (checkWin(row, col, piece)) {
|
||||
result = (piece == PieceType.BLACK) ? GameResult.BLACK_WIN : GameResult.WHITE_WIN;
|
||||
} else if (moveCount >= BOARD_SIZE * BOARD_SIZE) {
|
||||
result = GameResult.DRAW;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkWin(int row, int col, PieceType piece) {
|
||||
// Check horizontal
|
||||
if (checkDirection(row, col, 0, 1, piece) ||
|
||||
checkDirection(row, col, 0, -1, piece)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check vertical
|
||||
if (checkDirection(row, col, 1, 0, piece) ||
|
||||
checkDirection(row, col, -1, 0, piece)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check diagonal (top-left to bottom-right)
|
||||
if (checkDirection(row, col, 1, 1, piece) ||
|
||||
checkDirection(row, col, -1, -1, piece)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check diagonal (top-right to bottom-left)
|
||||
if (checkDirection(row, col, 1, -1, piece) ||
|
||||
checkDirection(row, col, -1, 1, piece)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkDirection(int row, int col, int deltaRow, int deltaCol, PieceType piece) {
|
||||
int count = 1; // Count the current piece
|
||||
|
||||
// Check in positive direction
|
||||
int r = row + deltaRow;
|
||||
int c = col + deltaCol;
|
||||
while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] == piece) {
|
||||
count++;
|
||||
r += deltaRow;
|
||||
c += deltaCol;
|
||||
}
|
||||
|
||||
// Check in negative direction
|
||||
r = row - deltaRow;
|
||||
c = col - deltaCol;
|
||||
while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] == piece) {
|
||||
count++;
|
||||
r -= deltaRow;
|
||||
c -= deltaCol;
|
||||
}
|
||||
|
||||
return count >= WIN_CONDITION;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.moveCount = 0;
|
||||
this.result = GameResult.IN_PROGRESS;
|
||||
initializeBoard();
|
||||
}
|
||||
|
||||
public PieceType getPiece(int row, int col) {
|
||||
if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE) {
|
||||
return board[row][col];
|
||||
}
|
||||
return PieceType.EMPTY;
|
||||
}
|
||||
|
||||
public PieceType[][] getBoard() {
|
||||
return board;
|
||||
}
|
||||
|
||||
public int getMoveCount() {
|
||||
return moveCount;
|
||||
}
|
||||
|
||||
public GameResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isGameOver() {
|
||||
return result != GameResult.IN_PROGRESS;
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,6 @@ public class ClientSide {
|
||||
|
||||
private String nickname;
|
||||
|
||||
private List<Poker> pokers;
|
||||
|
||||
private ClientStatus status;
|
||||
|
||||
private ClientRole role;
|
||||
@@ -48,7 +46,6 @@ public class ClientSide {
|
||||
|
||||
public void init() {
|
||||
roomId = 0;
|
||||
pokers = null;
|
||||
status = ClientStatus.TO_CHOOSE;
|
||||
type = null;
|
||||
next = null;
|
||||
@@ -100,13 +97,6 @@ public class ClientSide {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public final List<Poker> getPokers() {
|
||||
return pokers;
|
||||
}
|
||||
|
||||
public final void setPokers(List<Poker> pokers) {
|
||||
this.pokers = pokers;
|
||||
}
|
||||
|
||||
public final int getScore() {
|
||||
return score;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.nico.ratel.landlords.entity;
|
||||
|
||||
import org.nico.ratel.landlords.enums.PieceType;
|
||||
|
||||
public class GameMove {
|
||||
|
||||
private int row;
|
||||
private int col;
|
||||
private PieceType piece;
|
||||
private int playerId;
|
||||
private long timestamp;
|
||||
|
||||
public GameMove() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public GameMove(int row, int col, PieceType piece, int playerId) {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.piece = piece;
|
||||
this.playerId = playerId;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
public void setRow(int row) {
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
public void setCol(int col) {
|
||||
this.col = col;
|
||||
}
|
||||
|
||||
public PieceType getPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
public void setPiece(PieceType piece) {
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
public void setPlayerId(int playerId) {
|
||||
this.playerId = playerId;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GameMove{row=%d, col=%d, piece=%s, playerId=%d}",
|
||||
row, col, piece, playerId);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.nico.noson.annotations.JsonIgnore;
|
||||
import org.nico.ratel.landlords.enums.RoomStatus;
|
||||
import org.nico.ratel.landlords.enums.RoomType;
|
||||
import org.nico.ratel.landlords.enums.PieceType;
|
||||
|
||||
public class Room {
|
||||
|
||||
@@ -25,15 +26,15 @@ public class Room {
|
||||
|
||||
private LinkedList<ClientSide> clientSideList;
|
||||
|
||||
private int landlordId = -1;
|
||||
private Board gameBoard;
|
||||
|
||||
private List<Poker> landlordPokers;
|
||||
private int currentPlayer = -1;
|
||||
|
||||
private PokerSell lastPokerShell;
|
||||
private int blackPlayerId = -1;
|
||||
|
||||
private int lastSellClient = -1;
|
||||
private int whitePlayerId = -1;
|
||||
|
||||
private int currentSellClient = -1;
|
||||
private PieceType currentTurn = PieceType.BLACK;
|
||||
|
||||
private int difficultyCoefficient;
|
||||
|
||||
@@ -41,7 +42,7 @@ public class Room {
|
||||
|
||||
private long createTime;
|
||||
|
||||
private int firstSellClient;
|
||||
private List<GameMove> moveHistory;
|
||||
|
||||
/** List of spectators */
|
||||
private List<ClientSide> watcherList = new ArrayList<>(5);
|
||||
@@ -59,6 +60,9 @@ public class Room {
|
||||
this.clientSideList = new LinkedList<>();
|
||||
this.status = RoomStatus.WAIT;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
this.gameBoard = new Board();
|
||||
this.moveHistory = new ArrayList<>();
|
||||
this.currentTurn = PieceType.BLACK;
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
@@ -113,20 +117,20 @@ public class Room {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public final PokerSell getLastPokerShell() {
|
||||
return lastPokerShell;
|
||||
public final Board getGameBoard() {
|
||||
return gameBoard;
|
||||
}
|
||||
|
||||
public final void setLastPokerShell(PokerSell lastPokerShell) {
|
||||
this.lastPokerShell = lastPokerShell;
|
||||
public final void setGameBoard(Board gameBoard) {
|
||||
this.gameBoard = gameBoard;
|
||||
}
|
||||
|
||||
public final int getCurrentSellClient() {
|
||||
return currentSellClient;
|
||||
public final int getCurrentPlayer() {
|
||||
return currentPlayer;
|
||||
}
|
||||
|
||||
public final void setCurrentSellClient(int currentSellClient) {
|
||||
this.currentSellClient = currentSellClient;
|
||||
public final void setCurrentPlayer(int currentPlayer) {
|
||||
this.currentPlayer = currentPlayer;
|
||||
}
|
||||
|
||||
public long getLastFlushTime() {
|
||||
@@ -137,20 +141,32 @@ public class Room {
|
||||
this.lastFlushTime = lastFlushTime;
|
||||
}
|
||||
|
||||
public int getLastSellClient() {
|
||||
return lastSellClient;
|
||||
public int getBlackPlayerId() {
|
||||
return blackPlayerId;
|
||||
}
|
||||
|
||||
public void setLastSellClient(int lastSellClient) {
|
||||
this.lastSellClient = lastSellClient;
|
||||
public void setBlackPlayerId(int blackPlayerId) {
|
||||
this.blackPlayerId = blackPlayerId;
|
||||
}
|
||||
|
||||
public int getLandlordId() {
|
||||
return landlordId;
|
||||
public int getWhitePlayerId() {
|
||||
return whitePlayerId;
|
||||
}
|
||||
|
||||
public void setLandlordId(int landlordId) {
|
||||
this.landlordId = landlordId;
|
||||
public void setWhitePlayerId(int whitePlayerId) {
|
||||
this.whitePlayerId = whitePlayerId;
|
||||
}
|
||||
|
||||
public PieceType getCurrentTurn() {
|
||||
return currentTurn;
|
||||
}
|
||||
|
||||
public void setCurrentTurn(PieceType currentTurn) {
|
||||
this.currentTurn = currentTurn;
|
||||
}
|
||||
|
||||
public void switchTurn() {
|
||||
this.currentTurn = (this.currentTurn == PieceType.BLACK) ? PieceType.WHITE : PieceType.BLACK;
|
||||
}
|
||||
|
||||
public LinkedList<ClientSide> getClientSideList() {
|
||||
@@ -161,12 +177,22 @@ public class Room {
|
||||
this.clientSideList = clientSideList;
|
||||
}
|
||||
|
||||
public List<Poker> getLandlordPokers() {
|
||||
return landlordPokers;
|
||||
public List<GameMove> getMoveHistory() {
|
||||
return moveHistory;
|
||||
}
|
||||
|
||||
public void setLandlordPokers(List<Poker> landlordPokers) {
|
||||
this.landlordPokers = landlordPokers;
|
||||
public void setMoveHistory(List<GameMove> moveHistory) {
|
||||
this.moveHistory = moveHistory;
|
||||
}
|
||||
|
||||
public void addMove(GameMove move) {
|
||||
this.moveHistory.add(move);
|
||||
}
|
||||
|
||||
public void resetGame() {
|
||||
this.gameBoard.reset();
|
||||
this.moveHistory.clear();
|
||||
this.currentTurn = PieceType.BLACK;
|
||||
}
|
||||
|
||||
public final String getRoomOwner() {
|
||||
@@ -201,12 +227,18 @@ public class Room {
|
||||
this.clientSideMap = clientSideMap;
|
||||
}
|
||||
|
||||
public int getFirstSellClient() {
|
||||
return firstSellClient;
|
||||
public boolean isPlayerTurn(int playerId) {
|
||||
return (currentTurn == PieceType.BLACK && playerId == blackPlayerId) ||
|
||||
(currentTurn == PieceType.WHITE && playerId == whitePlayerId);
|
||||
}
|
||||
|
||||
public void setFirstSellClient(int firstSellClient) {
|
||||
this.firstSellClient = firstSellClient;
|
||||
public PieceType getPlayerPiece(int playerId) {
|
||||
if (playerId == blackPlayerId) {
|
||||
return PieceType.BLACK;
|
||||
} else if (playerId == whitePlayerId) {
|
||||
return PieceType.WHITE;
|
||||
}
|
||||
return PieceType.EMPTY;
|
||||
}
|
||||
|
||||
public List<ClientSide> getWatcherList() {
|
||||
|
||||
+13
-19
@@ -22,7 +22,7 @@ public enum ClientEventCode implements Serializable {
|
||||
|
||||
CODE_SHOW_ROOMS("Show room list"),
|
||||
|
||||
CODE_SHOW_POKERS("Show poker"),
|
||||
CODE_SHOW_BOARD("Show game board"),
|
||||
|
||||
CODE_ROOM_CREATE_SUCCESS("Room created successfully"),
|
||||
|
||||
@@ -32,34 +32,28 @@ public enum ClientEventCode implements Serializable {
|
||||
|
||||
CODE_ROOM_JOIN_FAIL_BY_INEXIST("Join - room does not exist"),
|
||||
|
||||
CODE_ROOM_PLAY_FAIL_BY_INEXIST1("Play card - room does not exist"),
|
||||
CODE_ROOM_PLAY_FAIL_BY_INEXIST("Make move - room does not exist"),
|
||||
|
||||
CODE_GAME_STARTING("Start game"),
|
||||
|
||||
CODE_GAME_LANDLORD_ELECT("Elect landlord"),
|
||||
CODE_GAME_MOVE_SUCCESS("Move successful"),
|
||||
|
||||
CODE_GAME_LANDLORD_CONFIRM("Landlord confirmed"),
|
||||
CODE_GAME_MOVE_INVALID("Invalid move"),
|
||||
|
||||
CODE_GAME_LANDLORD_CYCLE("Landlord election round ended"),
|
||||
CODE_GAME_MOVE_OCCUPIED("Position occupied"),
|
||||
|
||||
CODE_GAME_POKER_PLAY("Poker play turn"),
|
||||
CODE_GAME_MOVE_OUT_OF_BOUNDS("Move out of bounds"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_REDIRECT("Poker play redirect"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_MISMATCH("Poker play mismatch"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_LESS("Poker play too small"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_PASS("Pass"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_CANT_PASS("Cannot pass"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_INVALID("Invalid"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_ORDER_ERROR("Order error"),
|
||||
CODE_GAME_MOVE_NOT_YOUR_TURN("Not your turn"),
|
||||
|
||||
CODE_GAME_OVER("Game over"),
|
||||
|
||||
CODE_GAME_WIN("You win"),
|
||||
|
||||
CODE_GAME_LOSE("You lose"),
|
||||
|
||||
CODE_GAME_DRAW("Draw"),
|
||||
|
||||
CODE_PVE_DIFFICULTY_NOT_SUPPORT("AI difficulty not supported"),
|
||||
|
||||
CODE_GAME_READY("Ready to start game"),
|
||||
|
||||
@@ -4,6 +4,10 @@ public enum ClientRole {
|
||||
|
||||
PLAYER,
|
||||
|
||||
ROBOT
|
||||
ROBOT,
|
||||
|
||||
BLACK_PLAYER,
|
||||
|
||||
WHITE_PLAYER
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.nico.ratel.landlords.enums;
|
||||
|
||||
public enum GameResult {
|
||||
|
||||
BLACK_WIN("Black wins"),
|
||||
|
||||
WHITE_WIN("White wins"),
|
||||
|
||||
DRAW("Draw"),
|
||||
|
||||
IN_PROGRESS("Game in progress"),
|
||||
;
|
||||
|
||||
private String msg;
|
||||
|
||||
GameResult(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public final String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public final void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.nico.ratel.landlords.enums;
|
||||
|
||||
public enum PieceType {
|
||||
|
||||
EMPTY("Empty"),
|
||||
|
||||
BLACK("Black piece"),
|
||||
|
||||
WHITE("White piece"),
|
||||
;
|
||||
|
||||
private String msg;
|
||||
|
||||
PieceType(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public final String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public final void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.nico.ratel.landlords.enums;
|
||||
|
||||
public enum SellType {
|
||||
|
||||
ILLEGAL("Illegal"),
|
||||
|
||||
BOMB("Bomb"),
|
||||
|
||||
KING_BOMB("King bomb"),
|
||||
|
||||
SINGLE("Single card"),
|
||||
|
||||
DOUBLE("Pair"),
|
||||
|
||||
THREE("Three of a kind"),
|
||||
|
||||
THREE_ZONES_SINGLE("Three with single"),
|
||||
|
||||
THREE_ZONES_DOUBLE("Three with pair"),
|
||||
|
||||
FOUR_ZONES_SINGLE("Four with single"),
|
||||
|
||||
FOUR_ZONES_DOUBLE("Four with pair"),
|
||||
|
||||
SINGLE_STRAIGHT("Single straight"),
|
||||
|
||||
DOUBLE_STRAIGHT("Double straight"),
|
||||
|
||||
THREE_STRAIGHT("Triple straight"),
|
||||
|
||||
FOUR_STRAIGHT("Quadruple straight"),
|
||||
|
||||
THREE_STRAIGHT_WITH_SINGLE("Airplane with single cards"),
|
||||
|
||||
THREE_STRAIGHT_WITH_DOUBLE("Airplane with pairs"),
|
||||
|
||||
FOUR_STRAIGHT_WITH_SINGLE("Quadruple straight with single"),
|
||||
|
||||
FOUR_STRAIGHT_WITH_DOUBLE("Quadruple straight with pair"),
|
||||
;
|
||||
|
||||
private String msg;
|
||||
|
||||
SellType(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public final String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public final void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public enum ServerEventCode implements Serializable {
|
||||
|
||||
CODE_CLIENT_NICKNAME_SET("Set nickname"),
|
||||
|
||||
CODE_CLIENT_HEAD_BEAT("Pass"),
|
||||
CODE_CLIENT_HEAD_BEAT("Heartbeat"),
|
||||
|
||||
CODE_ROOM_CREATE("Create PVP room"),
|
||||
|
||||
@@ -26,13 +26,9 @@ public enum ServerEventCode implements Serializable {
|
||||
|
||||
CODE_GAME_READY("Player ready"),
|
||||
|
||||
CODE_GAME_LANDLORD_ELECT("Elect landlord"),
|
||||
CODE_GAME_MOVE("Make move"),
|
||||
|
||||
CODE_GAME_POKER_PLAY("Poker play phase"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_REDIRECT("Poker play redirect"),
|
||||
|
||||
CODE_GAME_POKER_PLAY_PASS("Pass"),
|
||||
CODE_GAME_RESET("Reset game"),
|
||||
|
||||
CODE_GAME_WATCH("Spectate"),
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package org.nico.ratel.landlords.helper;
|
||||
|
||||
import org.nico.ratel.landlords.entity.Board;
|
||||
import org.nico.ratel.landlords.entity.GameMove;
|
||||
import org.nico.ratel.landlords.entity.Room;
|
||||
import org.nico.ratel.landlords.enums.PieceType;
|
||||
import org.nico.ratel.landlords.enums.GameResult;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GomokuHelper {
|
||||
|
||||
public static boolean isValidMove(Room room, int row, int col, int playerId) {
|
||||
// Check if it's the player's turn
|
||||
if (!room.isPlayerTurn(playerId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the position is valid and empty
|
||||
return room.getGameBoard().isValidMove(row, col);
|
||||
}
|
||||
|
||||
public static GameResult makeMove(Room room, int row, int col, int playerId) {
|
||||
PieceType piece = room.getPlayerPiece(playerId);
|
||||
|
||||
if (piece == PieceType.EMPTY) {
|
||||
return GameResult.IN_PROGRESS;
|
||||
}
|
||||
|
||||
// Make the move on the board
|
||||
boolean success = room.getGameBoard().makeMove(row, col, piece);
|
||||
|
||||
if (success) {
|
||||
// Add to move history
|
||||
GameMove move = new GameMove(row, col, piece, playerId);
|
||||
room.addMove(move);
|
||||
|
||||
// Switch turns
|
||||
room.switchTurn();
|
||||
|
||||
return room.getGameBoard().getResult();
|
||||
}
|
||||
|
||||
return GameResult.IN_PROGRESS;
|
||||
}
|
||||
|
||||
public static String formatBoardForDisplay(Board board) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" ");
|
||||
|
||||
// Add column headers
|
||||
for (int i = 0; i < Board.BOARD_SIZE; i++) {
|
||||
sb.append(String.format("%2d", i));
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// Add rows
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
sb.append(String.format("%2d", row));
|
||||
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
PieceType piece = board.getPiece(row, col);
|
||||
char symbol = '.';
|
||||
|
||||
switch (piece) {
|
||||
case BLACK:
|
||||
symbol = 'B';
|
||||
break;
|
||||
case WHITE:
|
||||
symbol = 'W';
|
||||
break;
|
||||
case EMPTY:
|
||||
symbol = '.';
|
||||
break;
|
||||
}
|
||||
|
||||
sb.append(" ").append(symbol);
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatMoveHistory(List<GameMove> moves) {
|
||||
if (moves.isEmpty()) {
|
||||
return "No moves made yet.";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Move History:\n");
|
||||
|
||||
for (int i = 0; i < moves.size(); i++) {
|
||||
GameMove move = moves.get(i);
|
||||
sb.append(String.format("%d. %s at (%d,%d)\n",
|
||||
i + 1,
|
||||
move.getPiece().getMsg(),
|
||||
move.getRow(),
|
||||
move.getCol()));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static List<String> getValidMoves(Board board) {
|
||||
List<String> validMoves = new ArrayList<>();
|
||||
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
if (board.isValidMove(row, col)) {
|
||||
validMoves.add(row + "," + col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validMoves;
|
||||
}
|
||||
|
||||
public static boolean isGameOver(Room room) {
|
||||
return room.getGameBoard().isGameOver();
|
||||
}
|
||||
|
||||
public static GameResult getGameResult(Room room) {
|
||||
return room.getGameBoard().getResult();
|
||||
}
|
||||
|
||||
public static String getWinnerMessage(GameResult result) {
|
||||
switch (result) {
|
||||
case BLACK_WIN:
|
||||
return "Black player wins!";
|
||||
case WHITE_WIN:
|
||||
return "White player wins!";
|
||||
case DRAW:
|
||||
return "Game ended in a draw!";
|
||||
default:
|
||||
return "Game in progress...";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package org.nico.ratel.landlords.robot;
|
||||
|
||||
import org.nico.ratel.landlords.entity.Board;
|
||||
import org.nico.ratel.landlords.entity.GameMove;
|
||||
import org.nico.ratel.landlords.enums.PieceType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class GomokuAI {
|
||||
|
||||
private final Random random = new Random();
|
||||
private final PieceType aiPiece;
|
||||
private final PieceType opponentPiece;
|
||||
|
||||
public GomokuAI(PieceType aiPiece) {
|
||||
this.aiPiece = aiPiece;
|
||||
this.opponentPiece = (aiPiece == PieceType.BLACK) ? PieceType.WHITE : PieceType.BLACK;
|
||||
}
|
||||
|
||||
public GameMove getNextMove(Board board, int difficulty) {
|
||||
switch (difficulty) {
|
||||
case 1:
|
||||
return getEasyMove(board);
|
||||
case 2:
|
||||
return getMediumMove(board);
|
||||
case 3:
|
||||
return getHardMove(board);
|
||||
default:
|
||||
return getEasyMove(board);
|
||||
}
|
||||
}
|
||||
|
||||
private GameMove getEasyMove(Board board) {
|
||||
// Simple random move
|
||||
List<int[]> validMoves = getValidMoves(board);
|
||||
if (validMoves.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int[] move = validMoves.get(random.nextInt(validMoves.size()));
|
||||
return new GameMove(move[0], move[1], aiPiece, -1);
|
||||
}
|
||||
|
||||
private GameMove getMediumMove(Board board) {
|
||||
// Try to win first, then block opponent
|
||||
GameMove winMove = findWinningMove(board, aiPiece);
|
||||
if (winMove != null) {
|
||||
return winMove;
|
||||
}
|
||||
|
||||
GameMove blockMove = findWinningMove(board, opponentPiece);
|
||||
if (blockMove != null) {
|
||||
blockMove.setPiece(aiPiece);
|
||||
return blockMove;
|
||||
}
|
||||
|
||||
// Otherwise, make a strategic move
|
||||
return getStrategicMove(board);
|
||||
}
|
||||
|
||||
private GameMove getHardMove(Board board) {
|
||||
// Advanced AI with threat detection and strategic positioning
|
||||
GameMove winMove = findWinningMove(board, aiPiece);
|
||||
if (winMove != null) {
|
||||
return winMove;
|
||||
}
|
||||
|
||||
GameMove blockMove = findWinningMove(board, opponentPiece);
|
||||
if (blockMove != null) {
|
||||
blockMove.setPiece(aiPiece);
|
||||
return blockMove;
|
||||
}
|
||||
|
||||
// Look for threat patterns
|
||||
GameMove threatMove = findThreatMove(board);
|
||||
if (threatMove != null) {
|
||||
return threatMove;
|
||||
}
|
||||
|
||||
return getStrategicMove(board);
|
||||
}
|
||||
|
||||
private GameMove findWinningMove(Board board, PieceType piece) {
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
if (board.isValidMove(row, col)) {
|
||||
// Temporarily place piece
|
||||
Board tempBoard = copyBoard(board);
|
||||
tempBoard.makeMove(row, col, piece);
|
||||
|
||||
if (tempBoard.getResult().toString().contains("WIN")) {
|
||||
return new GameMove(row, col, aiPiece, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private GameMove findThreatMove(Board board) {
|
||||
// Look for positions that create multiple threats
|
||||
GameMove bestMove = null;
|
||||
int maxThreats = 0;
|
||||
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
if (board.isValidMove(row, col)) {
|
||||
int threats = countThreats(board, row, col, aiPiece);
|
||||
if (threats > maxThreats) {
|
||||
maxThreats = threats;
|
||||
bestMove = new GameMove(row, col, aiPiece, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
private int countThreats(Board board, int row, int col, PieceType piece) {
|
||||
int threats = 0;
|
||||
|
||||
// Check all 4 directions
|
||||
int[][] directions = {{0, 1}, {1, 0}, {1, 1}, {1, -1}};
|
||||
|
||||
for (int[] dir : directions) {
|
||||
int count = 1; // Count the piece we're about to place
|
||||
|
||||
// Count in positive direction
|
||||
int r = row + dir[0];
|
||||
int c = col + dir[1];
|
||||
while (r >= 0 && r < Board.BOARD_SIZE && c >= 0 && c < Board.BOARD_SIZE &&
|
||||
board.getPiece(r, c) == piece) {
|
||||
count++;
|
||||
r += dir[0];
|
||||
c += dir[1];
|
||||
}
|
||||
|
||||
// Count in negative direction
|
||||
r = row - dir[0];
|
||||
c = col - dir[1];
|
||||
while (r >= 0 && r < Board.BOARD_SIZE && c >= 0 && c < Board.BOARD_SIZE &&
|
||||
board.getPiece(r, c) == piece) {
|
||||
count++;
|
||||
r -= dir[0];
|
||||
c -= dir[1];
|
||||
}
|
||||
|
||||
if (count >= 3) {
|
||||
threats++;
|
||||
}
|
||||
}
|
||||
|
||||
return threats;
|
||||
}
|
||||
|
||||
private GameMove getStrategicMove(Board board) {
|
||||
// Prefer center positions and positions near existing pieces
|
||||
GameMove bestMove = null;
|
||||
int bestScore = -1;
|
||||
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
if (board.isValidMove(row, col)) {
|
||||
int score = evaluatePosition(board, row, col);
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMove = new GameMove(row, col, aiPiece, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove != null ? bestMove : getEasyMove(board);
|
||||
}
|
||||
|
||||
private int evaluatePosition(Board board, int row, int col) {
|
||||
int score = 0;
|
||||
|
||||
// Prefer center positions
|
||||
int centerDistance = Math.abs(row - Board.BOARD_SIZE / 2) + Math.abs(col - Board.BOARD_SIZE / 2);
|
||||
score += (Board.BOARD_SIZE - centerDistance) * 2;
|
||||
|
||||
// Prefer positions near existing pieces
|
||||
for (int r = Math.max(0, row - 2); r <= Math.min(Board.BOARD_SIZE - 1, row + 2); r++) {
|
||||
for (int c = Math.max(0, col - 2); c <= Math.min(Board.BOARD_SIZE - 1, col + 2); c++) {
|
||||
if (board.getPiece(r, c) != PieceType.EMPTY) {
|
||||
score += 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private List<int[]> getValidMoves(Board board) {
|
||||
List<int[]> moves = new ArrayList<>();
|
||||
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
if (board.isValidMove(row, col)) {
|
||||
moves.add(new int[]{row, col});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private Board copyBoard(Board original) {
|
||||
Board copy = new Board();
|
||||
for (int row = 0; row < Board.BOARD_SIZE; row++) {
|
||||
for (int col = 0; col < Board.BOARD_SIZE; col++) {
|
||||
PieceType piece = original.getPiece(row, col);
|
||||
if (piece != PieceType.EMPTY) {
|
||||
copy.makeMove(row, col, piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,16 @@
|
||||
pls_select_srv=Please select a server:
|
||||
srv_addr_not_exist=Server address does not exist!
|
||||
try_connect_%s_failed_%s=Failed to connect to server %s, reason: %s
|
||||
try_connect_%s_failed_%s=Failed to connect to server %s, reason: %s
|
||||
game_move_invalid=Invalid move! Please try again.
|
||||
game_move_occupied=Position already occupied!
|
||||
game_move_out_of_bounds=Move out of bounds!
|
||||
game_move_not_your_turn=It's not your turn!
|
||||
game_black_win=Black player wins!
|
||||
game_white_win=White player wins!
|
||||
game_draw=Game ended in a draw!
|
||||
board_display_title=Current Board:
|
||||
move_history_title=Move History:
|
||||
waiting_for_opponent=Waiting for opponent...
|
||||
your_turn=Your turn!
|
||||
spectator_joined=Spectator joined the game.
|
||||
spectator_left=Spectator left the game.
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package org.nico.ratel.landlords.helper.tests;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.nico.ratel.landlords.entity.Board;
|
||||
import org.nico.ratel.landlords.entity.Room;
|
||||
import org.nico.ratel.landlords.enums.PieceType;
|
||||
import org.nico.ratel.landlords.enums.GameResult;
|
||||
import org.nico.ratel.landlords.helper.GomokuHelper;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class GomokuHelperTest {
|
||||
|
||||
@Test
|
||||
public void testHorizontalWin() {
|
||||
Board board = new Board();
|
||||
|
||||
// Place 5 black pieces horizontally
|
||||
for (int col = 0; col < 5; col++) {
|
||||
assertTrue(board.makeMove(7, col, PieceType.BLACK));
|
||||
}
|
||||
|
||||
assertEquals(GameResult.BLACK_WIN, board.getResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerticalWin() {
|
||||
Board board = new Board();
|
||||
|
||||
// Place 5 black pieces vertically
|
||||
for (int row = 0; row < 5; row++) {
|
||||
assertTrue(board.makeMove(row, 7, PieceType.BLACK));
|
||||
}
|
||||
|
||||
assertEquals(GameResult.BLACK_WIN, board.getResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiagonalWin() {
|
||||
Board board = new Board();
|
||||
|
||||
// Place 5 black pieces diagonally
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertTrue(board.makeMove(i, i, PieceType.BLACK));
|
||||
}
|
||||
|
||||
assertEquals(GameResult.BLACK_WIN, board.getResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidMove() {
|
||||
Board board = new Board();
|
||||
|
||||
// Place a piece
|
||||
assertTrue(board.makeMove(7, 7, PieceType.BLACK));
|
||||
|
||||
// Try to place another piece in the same position
|
||||
assertFalse(board.makeMove(7, 7, PieceType.WHITE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutOfBounds() {
|
||||
Board board = new Board();
|
||||
|
||||
// Try to place piece outside board
|
||||
assertFalse(board.makeMove(-1, 7, PieceType.BLACK));
|
||||
assertFalse(board.makeMove(7, -1, PieceType.BLACK));
|
||||
assertFalse(board.makeMove(15, 7, PieceType.BLACK));
|
||||
assertFalse(board.makeMove(7, 15, PieceType.BLACK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoomPlayerTurn() {
|
||||
Room room = new Room(1);
|
||||
room.setBlackPlayerId(100);
|
||||
room.setWhitePlayerId(200);
|
||||
|
||||
// Initially black player's turn
|
||||
assertTrue(room.isPlayerTurn(100));
|
||||
assertFalse(room.isPlayerTurn(200));
|
||||
|
||||
// Switch turn
|
||||
room.switchTurn();
|
||||
assertFalse(room.isPlayerTurn(100));
|
||||
assertTrue(room.isPlayerTurn(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameMoveValidation() {
|
||||
Room room = new Room(1);
|
||||
room.setBlackPlayerId(100);
|
||||
room.setWhitePlayerId(200);
|
||||
|
||||
// Valid move for black player
|
||||
assertTrue(GomokuHelper.isValidMove(room, 7, 7, 100));
|
||||
|
||||
// Invalid move for white player (not their turn)
|
||||
assertFalse(GomokuHelper.isValidMove(room, 7, 8, 200));
|
||||
|
||||
// Make move and switch turn
|
||||
GomokuHelper.makeMove(room, 7, 7, 100);
|
||||
|
||||
// Now valid move for white player
|
||||
assertTrue(GomokuHelper.isValidMove(room, 7, 8, 200));
|
||||
|
||||
// Invalid move for black player (not their turn)
|
||||
assertFalse(GomokuHelper.isValidMove(room, 8, 7, 100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoardFormatting() {
|
||||
Board board = new Board();
|
||||
board.makeMove(7, 7, PieceType.BLACK);
|
||||
board.makeMove(7, 8, PieceType.WHITE);
|
||||
|
||||
String display = GomokuHelper.formatBoardForDisplay(board);
|
||||
|
||||
// Check that display contains the board
|
||||
assertNotNull(display);
|
||||
assertTrue(display.contains("B"));
|
||||
assertTrue(display.contains("W"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameReset() {
|
||||
Room room = new Room(1);
|
||||
room.setBlackPlayerId(100);
|
||||
room.setWhitePlayerId(200);
|
||||
|
||||
// Make some moves
|
||||
GomokuHelper.makeMove(room, 7, 7, 100);
|
||||
GomokuHelper.makeMove(room, 7, 8, 200);
|
||||
|
||||
assertEquals(2, room.getMoveHistory().size());
|
||||
|
||||
// Reset game
|
||||
room.resetGame();
|
||||
|
||||
assertEquals(0, room.getMoveHistory().size());
|
||||
assertEquals(GameResult.IN_PROGRESS, room.getGameBoard().getResult());
|
||||
assertEquals(PieceType.BLACK, room.getCurrentTurn());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user