mirror of
https://github.com/MADTeacher/go_basics.git
synced 2025-11-23 21:34:47 +02:00
рефакторинг игры
This commit is contained in:
@@ -8,3 +8,15 @@ const (
|
|||||||
Cross
|
Cross
|
||||||
Nought
|
Nought
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Возвращаем строковое представление фигуры
|
||||||
|
func (f BoardField) String() string {
|
||||||
|
switch f {
|
||||||
|
case Cross:
|
||||||
|
return "X"
|
||||||
|
case Nought:
|
||||||
|
return "O"
|
||||||
|
default:
|
||||||
|
return " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,20 +12,29 @@ import (
|
|||||||
"tic-tac-toe/network"
|
"tic-tac-toe/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client represents the client-side application.
|
// Объявление структуры клиента
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
// подключение к серверу
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
|
// игровое поле
|
||||||
board *b.Board
|
board *b.Board
|
||||||
|
// фигура игрока
|
||||||
mySymbol b.BoardField
|
mySymbol b.BoardField
|
||||||
|
// фигура игрока, ход которой сейчас
|
||||||
currentPlayer b.BoardField
|
currentPlayer b.BoardField
|
||||||
|
// никнейм игрока
|
||||||
playerName string
|
playerName string
|
||||||
|
// имя комнаты
|
||||||
roomName string
|
roomName string
|
||||||
|
// текущее состояние клиента
|
||||||
state State
|
state State
|
||||||
|
// мьютекс для защиты доступа к данным
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
// время последнего сообщения
|
||||||
lastMsgTime time.Time
|
lastMsgTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new client and connects to the server.
|
// Констукторная функция для создания клиента
|
||||||
func NewClient(addr string) (*Client, error) {
|
func NewClient(addr string) (*Client, error) {
|
||||||
conn, err := net.Dial("tcp", addr)
|
conn, err := net.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -33,77 +42,98 @@ func NewClient(addr string) (*Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
|
// подключение к серверу
|
||||||
conn: conn,
|
conn: conn,
|
||||||
|
// начальное состояние клиента
|
||||||
state: waitNickNameConfirm,
|
state: waitNickNameConfirm,
|
||||||
mySymbol: b.Empty, // Will be set upon joining a room
|
// mySymbol будет установлен при подключении к комнате
|
||||||
|
mySymbol: b.Empty,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Устанавливаем никнейм игрока
|
||||||
func (c *Client) setNickname(nickname string) {
|
func (c *Client) setNickname(nickname string) {
|
||||||
c.playerName = nickname
|
c.playerName = nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Получаем текущее состояние клиента
|
||||||
func (c *Client) getState() State {
|
func (c *Client) getState() State {
|
||||||
c.mutex.RLock()
|
c.mutex.RLock() // защищаем доступ к данным
|
||||||
defer c.mutex.RUnlock()
|
defer c.mutex.RUnlock()
|
||||||
return c.state
|
return c.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Устанавливаем текущее состояние клиента
|
||||||
func (c *Client) setState(state State) {
|
func (c *Client) setState(state State) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock() // защищаем доступ к данным
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
// Display a message only when transitioning to opponentMove
|
// Если переходим в состояние opponentMove
|
||||||
if state == opponentMove && c.state != opponentMove {
|
if state == opponentMove && c.state != opponentMove {
|
||||||
fmt.Println("\nWaiting for opponent's move...")
|
fmt.Println("\nWaiting for opponent's move...")
|
||||||
} else if state == waitingOpponentInRoom && c.state != waitingOpponentInRoom {
|
} else if state == waitingOpponentInRoom &&
|
||||||
|
c.state != waitingOpponentInRoom {
|
||||||
|
// Если переходим в состояние waitingOpponentInRoom
|
||||||
fmt.Println("\nWaiting for opponent to join...")
|
fmt.Println("\nWaiting for opponent to join...")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state = state
|
c.state = state // устанавливаем новое состояние
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start begins the client's main loop for sending and receiving messages.
|
// Запускаем клиента
|
||||||
func (c *Client) Start() {
|
func (c *Client) Start() {
|
||||||
defer c.conn.Close()
|
defer c.conn.Close()
|
||||||
|
|
||||||
fmt.Println("Connected to server. ")
|
fmt.Println("Connected to server. ")
|
||||||
|
// Запускаем горутину для чтения сообщений от сервера
|
||||||
go c.readFromServer()
|
go c.readFromServer()
|
||||||
|
// Запускаем меню клиента для взаимодействия с пользователем
|
||||||
c.menu()
|
c.menu()
|
||||||
}
|
}
|
||||||
|
|
||||||
// readFromServer continuously reads messages from the server and handles them.
|
// Читаем сообщения от сервера
|
||||||
func (c *Client) readFromServer() {
|
func (c *Client) readFromServer() {
|
||||||
decoder := json.NewDecoder(c.conn)
|
decoder := json.NewDecoder(c.conn) // Создаем декодер
|
||||||
for {
|
for { // Бесконечный цикл
|
||||||
var msg network.Message
|
var msg network.Message
|
||||||
if err := decoder.Decode(&msg); err != nil {
|
if err := decoder.Decode(&msg); err != nil {
|
||||||
log.Printf("Disconnected from server: %v", err)
|
log.Printf("Disconnected from server: %v", err)
|
||||||
return // Exit goroutine if connection is lost
|
return // если соединение потеряно, то выходим из горутины
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.Cmd {
|
switch msg.Cmd {
|
||||||
|
// Обрабатываем ответ на запрос на присоединение к комнате
|
||||||
case network.CmdRoomJoinResponse:
|
case network.CmdRoomJoinResponse:
|
||||||
c.handleRoomJoinResponse(msg.Payload)
|
c.handleRoomJoinResponse(msg.Payload)
|
||||||
|
// Обрабатываем сообщение об инициализацию игры
|
||||||
case network.CmdInitGame:
|
case network.CmdInitGame:
|
||||||
c.handleInitGame(msg.Payload)
|
c.handleInitGame(msg.Payload)
|
||||||
|
// Обрабатываем сообщение об обновлении состояния игры
|
||||||
case network.CmdUpdateState:
|
case network.CmdUpdateState:
|
||||||
c.handleUpdateState(msg.Payload)
|
c.handleUpdateState(msg.Payload)
|
||||||
|
// Обрабатываем сообщение об окончании игры
|
||||||
case network.CmdEndGame:
|
case network.CmdEndGame:
|
||||||
c.handleEndGame(msg.Payload)
|
c.handleEndGame(msg.Payload)
|
||||||
|
// Обрабатываем сообщение об ошибке
|
||||||
case network.CmdError:
|
case network.CmdError:
|
||||||
c.handleError(msg.Payload)
|
c.handleError(msg.Payload)
|
||||||
|
// Обрабатываем сообщение о списке комнат
|
||||||
case network.CmdRoomListResponse:
|
case network.CmdRoomListResponse:
|
||||||
c.handleRoomListResponse(msg.Payload)
|
c.handleRoomListResponse(msg.Payload)
|
||||||
|
// Обрабатываем сообщение о подтверждении никнейма
|
||||||
case network.CmdNickNameResponse:
|
case network.CmdNickNameResponse:
|
||||||
c.handleNickNameResponse(msg.Payload)
|
c.handleNickNameResponse(msg.Payload)
|
||||||
|
// Обрабатываем сообщение об отключении оппонента
|
||||||
case network.CmdOpponentLeft:
|
case network.CmdOpponentLeft:
|
||||||
c.handleOpponentLeft(msg.Payload)
|
c.handleOpponentLeft(msg.Payload)
|
||||||
|
// Обрабатываем сообщение о списке завершенных игр
|
||||||
case network.CmdFinishedGamesResponse:
|
case network.CmdFinishedGamesResponse:
|
||||||
c.handleFinishedGamesResponse(msg.Payload)
|
c.handleFinishedGamesResponse(msg.Payload)
|
||||||
|
// Обрабатываем сообщение с данными по
|
||||||
|
// запрошенной завершенной игре
|
||||||
case network.CmdFinishedGameResponse:
|
case network.CmdFinishedGameResponse:
|
||||||
c.handleFinishedGameResponse(msg.Payload)
|
c.handleFinishedGameResponse(msg.Payload)
|
||||||
default:
|
default: // Если пришло неизвестное сообщение
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"Received unhandled message type '%s' "+
|
"Received unhandled message type '%s' "+
|
||||||
"from server. Payload: %s\n> ",
|
"from server. Payload: %s\n> ",
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ package client
|
|||||||
type State int
|
type State int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// ожидание подтверждения никнейма от сервера
|
||||||
waitNickNameConfirm State = iota
|
waitNickNameConfirm State = iota
|
||||||
|
// главное меню
|
||||||
mainMenu
|
mainMenu
|
||||||
waitRoomJoin
|
// ход игрока
|
||||||
playing
|
|
||||||
playerMove
|
playerMove
|
||||||
|
// ход оппонента
|
||||||
opponentMove
|
opponentMove
|
||||||
|
// конец игры
|
||||||
endGame
|
endGame
|
||||||
|
// ожидание присоединения оппонента
|
||||||
waitingOpponentInRoom
|
waitingOpponentInRoom
|
||||||
|
// ожидание ответа от сервера
|
||||||
waitResponseFromServer
|
waitResponseFromServer
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,52 +12,74 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Метод для взаимодействия с пользователем
|
||||||
func (c *Client) menu() {
|
func (c *Client) menu() {
|
||||||
|
// Создаем буфер для чтения ввода
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
// Создаем энкодер для отправки сообщений
|
||||||
encoder := json.NewEncoder(c.conn)
|
encoder := json.NewEncoder(c.conn)
|
||||||
|
|
||||||
|
// Запрашиваем никнейм у пользователя
|
||||||
fmt.Print("Enter your nickname: ")
|
fmt.Print("Enter your nickname: ")
|
||||||
input, _ := reader.ReadString('\n')
|
input, _ := reader.ReadString('\n')
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
c.playerName = input
|
c.playerName = input
|
||||||
var msg network.Message
|
var msg network.Message
|
||||||
|
|
||||||
|
// Формируем сообщение и отправляем никнейм на сервер
|
||||||
msg.Cmd = network.CmdNickname
|
msg.Cmd = network.CmdNickname
|
||||||
payloadData := network.NicknameRequest{Nickname: c.playerName}
|
payloadData := network.NicknameRequest{Nickname: c.playerName}
|
||||||
jsonPayload, err := json.Marshal(payloadData)
|
jsonPayload, err := json.Marshal(payloadData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshalling payload for command %s: %v", msg.Cmd, err)
|
log.Printf(
|
||||||
|
"Error marshalling payload for command %s: %v",
|
||||||
|
msg.Cmd, err,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg.Payload = jsonPayload
|
msg.Payload = jsonPayload
|
||||||
|
// Отправляем сообщение на сервер
|
||||||
if err := encoder.Encode(msg); err != nil {
|
if err := encoder.Encode(msg); err != nil {
|
||||||
log.Printf("Failed to send message to server: %v. Disconnecting.", err)
|
log.Printf(
|
||||||
return // Exit if we can't send to server
|
"Failed to send message to server: %v. Disconnecting.",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
// Выходим из программы, если не удалось отправить сообщение
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for { // Бесконечный цикл
|
||||||
switch c.getState() {
|
switch c.getState() { // Переключение состояний
|
||||||
case waitNickNameConfirm:
|
case waitNickNameConfirm: // Ожидание подтверждения никнейма
|
||||||
|
// Ожидаем подтверждения никнейма от сервера
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
continue
|
continue
|
||||||
case mainMenu:
|
case mainMenu: // Главное меню
|
||||||
|
// Переходим в главное меню
|
||||||
c.mainMenu(reader, encoder)
|
c.mainMenu(reader, encoder)
|
||||||
case playerMove:
|
case playerMove: // Ход игрока
|
||||||
|
// Отрабатываем ход игрока
|
||||||
c.playing(reader, encoder)
|
c.playing(reader, encoder)
|
||||||
case opponentMove:
|
case opponentMove: // Ход противника
|
||||||
// Just wait silently for opponent's move
|
// Ожидаем данные по ходу противника
|
||||||
time.Sleep(1000 * time.Millisecond)
|
time.Sleep(1000 * time.Millisecond)
|
||||||
continue
|
continue
|
||||||
case endGame:
|
case endGame: // Конец игры
|
||||||
|
// Игра завершена. Ждем ее перезапуск от сервера
|
||||||
fmt.Println("\nGame has ended. Restarting in 10 seconds...")
|
fmt.Println("\nGame has ended. Restarting in 10 seconds...")
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
continue
|
continue
|
||||||
case waitResponseFromServer:
|
case waitResponseFromServer: // Ожидание ответа от сервера
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
continue
|
continue
|
||||||
case waitingOpponentInRoom:
|
case waitingOpponentInRoom: // Ожидание противника в комнате
|
||||||
// Rate-limit messages to once every 3 seconds
|
// Здесь нам надо учесть ситуацию, сто противник может
|
||||||
|
// так и не подключиться к комнате. Поэтому, чтобы не
|
||||||
|
// заставлять игрока страдать в бесконечном цикле ожидания
|
||||||
|
// мы ограничиваем сообщения 1 раз в 3 секунды и считываем
|
||||||
|
// ввод пользователя посредством неблокирующего чтения
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
// Если прошло более 3 секунд с момента последнего сообщения
|
||||||
if now.Sub(c.lastMsgTime) > 3*time.Second {
|
if now.Sub(c.lastMsgTime) > 3*time.Second {
|
||||||
c.lastMsgTime = now
|
c.lastMsgTime = now
|
||||||
fmt.Println("\nWaiting for opponent to join...")
|
fmt.Println("\nWaiting for opponent to join...")
|
||||||
@@ -65,11 +87,15 @@ func (c *Client) menu() {
|
|||||||
fmt.Print("> ")
|
fmt.Print("> ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll for input every cycle but don't block
|
// Проверяем ввод пользователя
|
||||||
var buffer [1]byte
|
var buffer [1]byte
|
||||||
n, _ := os.Stdin.Read(buffer[:])
|
n, _ := os.Stdin.Read(buffer[:])
|
||||||
|
// Если пользователь нажал 'q' или 'Q',
|
||||||
|
// то выходим в главное меню
|
||||||
if n > 0 && (buffer[0] == 'q' || buffer[0] == 'Q') {
|
if n > 0 && (buffer[0] == 'q' || buffer[0] == 'Q') {
|
||||||
fmt.Println("Leaving room...")
|
fmt.Println("Leaving room...")
|
||||||
|
// Формируем сообщение о выходе из комнаты
|
||||||
|
// и отправляем на сервер
|
||||||
var msg network.Message
|
var msg network.Message
|
||||||
msg.Cmd = network.CmdLeaveRoomRequest
|
msg.Cmd = network.CmdLeaveRoomRequest
|
||||||
payload := network.LeaveRoomRequest{
|
payload := network.LeaveRoomRequest{
|
||||||
@@ -78,21 +104,23 @@ func (c *Client) menu() {
|
|||||||
}
|
}
|
||||||
jsonPayload, _ := json.Marshal(payload)
|
jsonPayload, _ := json.Marshal(payload)
|
||||||
msg.Payload = jsonPayload
|
msg.Payload = jsonPayload
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg) // Отправляем сообщение на сервер
|
||||||
c.setState(mainMenu)
|
c.setState(mainMenu) // Переходим в главное меню
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short sleep to avoid CPU spinning
|
// Быстрый сон для избежания загрузки процессора
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Главное меню клиента для взаимодействия с пользователем
|
||||||
func (c *Client) mainMenu(reader *bufio.Reader, encoder *json.Encoder) {
|
func (c *Client) mainMenu(reader *bufio.Reader, encoder *json.Encoder) {
|
||||||
var msg network.Message
|
var msg network.Message // Создаем буфер для сообщения
|
||||||
|
|
||||||
|
// Выводим меню
|
||||||
fmt.Println("Enter command:")
|
fmt.Println("Enter command:")
|
||||||
fmt.Println("1 - Get room list")
|
fmt.Println("1 - Get room list")
|
||||||
fmt.Println("2 - Join room")
|
fmt.Println("2 - Join room")
|
||||||
@@ -100,24 +128,31 @@ func (c *Client) mainMenu(reader *bufio.Reader, encoder *json.Encoder) {
|
|||||||
fmt.Println("4 - Get finished game by id")
|
fmt.Println("4 - Get finished game by id")
|
||||||
fmt.Println("5 - Exit")
|
fmt.Println("5 - Exit")
|
||||||
fmt.Print("> ")
|
fmt.Print("> ")
|
||||||
|
// Считываем ввод пользователя
|
||||||
input, _ := reader.ReadString('\n')
|
input, _ := reader.ReadString('\n')
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
|
|
||||||
|
// Преобразуем ввод пользователя в число
|
||||||
command, err := strconv.Atoi(input)
|
command, err := strconv.Atoi(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Invalid command.")
|
fmt.Println("Invalid command.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем ввод пользователя
|
||||||
switch command {
|
switch command {
|
||||||
case 1:
|
case 1: // Получаем список комнат
|
||||||
|
// Формируем сообщение и отправляем на сервер
|
||||||
msg.Cmd = network.CmdListRoomsRequest
|
msg.Cmd = network.CmdListRoomsRequest
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg)
|
||||||
|
// Переходим в состояние ожидания ответа от сервера
|
||||||
c.setState(waitResponseFromServer)
|
c.setState(waitResponseFromServer)
|
||||||
case 2:
|
case 2: // Присоединяемся к комнате
|
||||||
|
// Запрашиваем имя комнаты у пользователя
|
||||||
fmt.Print("Enter room name: ")
|
fmt.Print("Enter room name: ")
|
||||||
input, _ := reader.ReadString('\n')
|
input, _ := reader.ReadString('\n')
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
|
// Формируем сообщение и отправляем на сервер
|
||||||
c.roomName = input
|
c.roomName = input
|
||||||
msg.Cmd = network.CmdJoinRoomRequest
|
msg.Cmd = network.CmdJoinRoomRequest
|
||||||
payload := network.JoinRoomRequest{
|
payload := network.JoinRoomRequest{
|
||||||
@@ -126,32 +161,39 @@ func (c *Client) mainMenu(reader *bufio.Reader, encoder *json.Encoder) {
|
|||||||
}
|
}
|
||||||
jsonPayload, _ := json.Marshal(payload)
|
jsonPayload, _ := json.Marshal(payload)
|
||||||
msg.Payload = jsonPayload
|
msg.Payload = jsonPayload
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg) // Отправляем сообщение на сервер
|
||||||
|
// Переходим в состояние ожидания ответа от сервера
|
||||||
c.setState(waitResponseFromServer)
|
c.setState(waitResponseFromServer)
|
||||||
case 3:
|
case 3: // Получаем список завершенных игр
|
||||||
|
// Формируем сообщение и отправляем на сервер
|
||||||
msg.Cmd = network.CmdFinishedGamesRequest
|
msg.Cmd = network.CmdFinishedGamesRequest
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg)
|
||||||
|
// Переходим в состояние ожидания ответа от сервера
|
||||||
c.setState(waitResponseFromServer)
|
c.setState(waitResponseFromServer)
|
||||||
case 4:
|
case 4: // Получаем завершенную игру по id
|
||||||
|
// Запрашиваем id игры у пользователя
|
||||||
fmt.Print("Enter game id: ")
|
fmt.Print("Enter game id: ")
|
||||||
input, _ := reader.ReadString('\n')
|
input, _ := reader.ReadString('\n')
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
|
|
||||||
|
// Преобразуем ввод пользователя в число
|
||||||
gameId, err := strconv.Atoi(input)
|
gameId, err := strconv.Atoi(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Invalid game id.")
|
fmt.Println("Invalid game id.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Формируем сообщение и отправляем на сервер
|
||||||
msg.Cmd = network.CmdFinishedGameByIdRequest
|
msg.Cmd = network.CmdFinishedGameByIdRequest
|
||||||
payload := network.GetFinishedGameByIdRequest{GameID: gameId}
|
payload := network.GetFinishedGameByIdRequest{GameID: gameId}
|
||||||
jsonPayload, _ := json.Marshal(payload)
|
jsonPayload, _ := json.Marshal(payload)
|
||||||
msg.Payload = jsonPayload
|
msg.Payload = jsonPayload
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg) // Отправляем сообщение на сервер
|
||||||
|
// Переходим в состояние ожидания ответа от сервера
|
||||||
c.setState(waitResponseFromServer)
|
c.setState(waitResponseFromServer)
|
||||||
case 5:
|
case 5: // Выходим из программы
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
default:
|
default: // Неизвестная команда
|
||||||
fmt.Println("Unknown command.")
|
fmt.Println("Unknown command.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,32 +9,38 @@ import (
|
|||||||
"tic-tac-toe/network"
|
"tic-tac-toe/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Обрабатываем ход игрока
|
||||||
func (c *Client) playing(reader *bufio.Reader, encoder *json.Encoder) {
|
func (c *Client) playing(reader *bufio.Reader, encoder *json.Encoder) {
|
||||||
fmt.Printf("\nEnter command: <row> <col> or q for exit to main menu\n> ")
|
fmt.Printf(
|
||||||
|
"\nEnter command: <row> <col> or q for exit to main menu\n> ",
|
||||||
|
)
|
||||||
|
// Считываем ввод игрока
|
||||||
input, _ := reader.ReadString('\n')
|
input, _ := reader.ReadString('\n')
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
|
|
||||||
if input == "q" {
|
if input == "q" { // Если игрок хочет выйти в меню
|
||||||
var msg network.Message
|
var msg network.Message // Создаем сообщение
|
||||||
msg.Cmd = network.CmdLeaveRoomRequest
|
msg.Cmd = network.CmdLeaveRoomRequest // Устанавливаем команду
|
||||||
payload := network.LeaveRoomRequest{
|
payload := network.LeaveRoomRequest{
|
||||||
RoomName: c.roomName,
|
RoomName: c.roomName,
|
||||||
PlayerName: c.playerName,
|
PlayerName: c.playerName,
|
||||||
}
|
}
|
||||||
jsonPayload, _ := json.Marshal(payload)
|
jsonPayload, _ := json.Marshal(payload)
|
||||||
msg.Payload = jsonPayload
|
msg.Payload = jsonPayload
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg) // Отправляем сообщение
|
||||||
c.setState(mainMenu)
|
c.setState(mainMenu) // Переходим в главное меню
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Разделяем ввод игрока на строки
|
||||||
parts := strings.Fields(input)
|
parts := strings.Fields(input)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
fmt.Println("Usage: <row> <col>")
|
fmt.Println("Usage: <row> <col>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg network.Message
|
var msg network.Message // Создаем сообщение
|
||||||
|
// Преобразуем ввод игрока в числа
|
||||||
row, err1 := strconv.Atoi(parts[0])
|
row, err1 := strconv.Atoi(parts[0])
|
||||||
col, err2 := strconv.Atoi(parts[1])
|
col, err2 := strconv.Atoi(parts[1])
|
||||||
if err1 != nil || err2 != nil {
|
if err1 != nil || err2 != nil {
|
||||||
@@ -42,19 +48,22 @@ func (c *Client) playing(reader *bufio.Reader, encoder *json.Encoder) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Валидируем ввод игрока
|
||||||
if !c.validateMove(row, col) {
|
if !c.validateMove(row, col) {
|
||||||
return // validateMove prints the error
|
return // validateMove prints the error
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Cmd = network.CmdMakeMoveRequest
|
// Создаем сообщение о ходе игрока
|
||||||
|
msg.Cmd = network.CmdMakeMoveRequest // Устанавливаем команду
|
||||||
payload := network.MakeMoveRequest{
|
payload := network.MakeMoveRequest{
|
||||||
RoomName: c.roomName,
|
RoomName: c.roomName, // Устанавливаем имя комнаты
|
||||||
PlayerName: c.playerName,
|
PlayerName: c.playerName, // Устанавливаем никнейм игрока
|
||||||
PositionRow: row - 1,
|
PositionRow: row - 1, // Устанавливаем строку
|
||||||
PositionCol: col - 1,
|
PositionCol: col - 1, // Устанавливаем столбец
|
||||||
}
|
}
|
||||||
jsonPayload, _ := json.Marshal(payload)
|
jsonPayload, _ := json.Marshal(payload)
|
||||||
msg.Payload = jsonPayload
|
msg.Payload = jsonPayload
|
||||||
encoder.Encode(msg)
|
encoder.Encode(msg) // Отправляем сообщение
|
||||||
|
// Переходим в состояние ожидания ответа от сервера
|
||||||
c.setState(waitResponseFromServer)
|
c.setState(waitResponseFromServer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,39 +5,47 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
b "tic-tac-toe/board"
|
b "tic-tac-toe/board" // Added for g.GameMode
|
||||||
g "tic-tac-toe/game" // Added for g.GameMode
|
|
||||||
"tic-tac-toe/network"
|
"tic-tac-toe/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleRoomJoinResponse processes the RoomJoinResponse message from the server.
|
// Обрабатываем ответ на запрос на присоединение к комнате
|
||||||
func (c *Client) handleRoomJoinResponse(payload json.RawMessage) {
|
func (c *Client) handleRoomJoinResponse(payload json.RawMessage) {
|
||||||
var res network.RoomJoinResponse
|
var res network.RoomJoinResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
c.mySymbol = res.PlayerSymbol
|
c.mySymbol = res.PlayerSymbol // Устанавливаем фигуру игрока
|
||||||
c.roomName = res.RoomName
|
c.roomName = res.RoomName // Устанавливаем имя комнаты
|
||||||
if res.Board.Size > 0 { // Check if board is valid
|
if res.Board.Size > 0 { // Проверяем размер поля
|
||||||
c.board = &res.Board
|
c.board = &res.Board // Устанавливаем игровое поле
|
||||||
fmt.Printf("\nSuccessfully joined room '%s' as %s.\n", res.RoomName, res.PlayerSymbol)
|
fmt.Printf(
|
||||||
|
"\nSuccessfully joined room '%s' as %s.\n",
|
||||||
|
res.RoomName, res.PlayerSymbol,
|
||||||
|
)
|
||||||
c.board.PrintBoard()
|
c.board.PrintBoard()
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\nSuccessfully joined room '%s' as %s. Waiting for game to start...\n", res.RoomName, res.PlayerSymbol)
|
fmt.Printf(
|
||||||
|
"\nSuccessfully joined room '%s' as %s. "+
|
||||||
|
"Waiting for game to start...\n",
|
||||||
|
res.RoomName, res.PlayerSymbol,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error unmarshalling RoomJoinResponse: %v", err)
|
log.Printf("Error unmarshalling RoomJoinResponse: %v", err)
|
||||||
}
|
}
|
||||||
|
// Устанавливаем состояние клиента
|
||||||
c.setState(waitingOpponentInRoom)
|
c.setState(waitingOpponentInRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleInitGame processes the InitGameResponse message from the server.
|
// Обрабатываем ответ на запрос на инициализацию игры
|
||||||
func (c *Client) handleInitGame(payload json.RawMessage) {
|
func (c *Client) handleInitGame(payload json.RawMessage) {
|
||||||
var res network.InitGameResponse
|
var res network.InitGameResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
c.board = &res.Board
|
c.board = &res.Board // Устанавливаем игровое поле
|
||||||
c.currentPlayer = res.CurrentPlayer
|
c.currentPlayer = res.CurrentPlayer // Устанавливаем фигуру игрока
|
||||||
fmt.Println("\n--- Game Started ---")
|
fmt.Println("\n--- Game Started ---")
|
||||||
c.board.PrintBoard()
|
c.board.PrintBoard() // Выводим игровое поле
|
||||||
c.printTurnInfo()
|
c.printTurnInfo() // Выводим информацию о ходе игрока
|
||||||
|
// Устанавливаем состояние клиента
|
||||||
if res.CurrentPlayer == c.mySymbol {
|
if res.CurrentPlayer == c.mySymbol {
|
||||||
c.setState(playerMove)
|
c.setState(playerMove)
|
||||||
} else {
|
} else {
|
||||||
@@ -48,15 +56,16 @@ func (c *Client) handleInitGame(payload json.RawMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateState processes the GameStateUpdate message from the server.
|
// Обрабатываем сообщение об обновлении состояния игры
|
||||||
func (c *Client) handleUpdateState(payload json.RawMessage) {
|
func (c *Client) handleUpdateState(payload json.RawMessage) {
|
||||||
var res network.GameStateUpdate
|
var res network.GameStateUpdate // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
c.board = &res.Board
|
c.board = &res.Board // Устанавливаем игровое поле
|
||||||
c.currentPlayer = res.CurrentPlayer
|
c.currentPlayer = res.CurrentPlayer // Устанавливаем фигуру игрока
|
||||||
fmt.Println("\n--- Game State Update ---")
|
fmt.Println("\n--- Game State Update ---")
|
||||||
c.board.PrintBoard()
|
c.board.PrintBoard() // Выводим игровое поле
|
||||||
c.printTurnInfo()
|
c.printTurnInfo() // Выводим информацию о ходе игрока
|
||||||
|
// Устанавливаем состояние клиента
|
||||||
if res.CurrentPlayer == c.mySymbol {
|
if res.CurrentPlayer == c.mySymbol {
|
||||||
c.setState(playerMove)
|
c.setState(playerMove)
|
||||||
} else {
|
} else {
|
||||||
@@ -67,28 +76,29 @@ func (c *Client) handleUpdateState(payload json.RawMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleEndGame processes the EndGameResponse message from the server.
|
// Обрабатываем сообщение об окончании игры
|
||||||
func (c *Client) handleEndGame(payload json.RawMessage) {
|
func (c *Client) handleEndGame(payload json.RawMessage) {
|
||||||
var res network.EndGameResponse
|
var res network.EndGameResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
c.board = &res.Board
|
c.board = &res.Board // Устанавливаем игровое поле
|
||||||
fmt.Println("\n--- Game Over ---")
|
fmt.Println("\n--- Game Over ---")
|
||||||
c.board.PrintBoard()
|
c.board.PrintBoard() // Выводим игровое поле
|
||||||
|
// Выводим информацию о победителе
|
||||||
if res.CurrentPlayer == b.Empty {
|
if res.CurrentPlayer == b.Empty {
|
||||||
fmt.Println("It's a Draw!")
|
fmt.Println("It's a Draw!")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Player %s wins!\n", res.CurrentPlayer)
|
fmt.Printf("Player %s wins!\n", res.CurrentPlayer)
|
||||||
}
|
}
|
||||||
c.setState(endGame)
|
c.setState(endGame) // Устанавливаем состояние клиента
|
||||||
fmt.Print("> ")
|
fmt.Print("> ")
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error unmarshalling EndGameResponse: %v", err)
|
log.Printf("Error unmarshalling EndGameResponse: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleError processes the ErrorResponse message from the server.
|
// Обрабатываем сообщение об ошибке
|
||||||
func (c *Client) handleError(payload json.RawMessage) {
|
func (c *Client) handleError(payload json.RawMessage) {
|
||||||
var errPayload network.ErrorResponse
|
var errPayload network.ErrorResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &errPayload); err == nil {
|
if err := json.Unmarshal(payload, &errPayload); err == nil {
|
||||||
fmt.Printf("\nServer Error: %s\n> ", errPayload.Message)
|
fmt.Printf("\nServer Error: %s\n> ", errPayload.Message)
|
||||||
} else {
|
} else {
|
||||||
@@ -97,41 +107,18 @@ func (c *Client) handleError(payload json.RawMessage) {
|
|||||||
c.setState(mainMenu)
|
c.setState(mainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gameModeToString converts GameMode to a string representation.
|
// Обрабатываем сообщение о списке комнат
|
||||||
func gameModeToString(mode g.GameMode) string {
|
|
||||||
switch mode {
|
|
||||||
case g.PvP:
|
|
||||||
return "PvP"
|
|
||||||
case g.PvC:
|
|
||||||
return "PvC"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func difficultyToString(difficulty g.Difficulty) string {
|
|
||||||
switch difficulty {
|
|
||||||
case g.Easy:
|
|
||||||
return "Easy"
|
|
||||||
case g.Medium:
|
|
||||||
return "Medium"
|
|
||||||
case g.Hard:
|
|
||||||
return "Hard"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleRoomListResponse processes the RoomListResponse message from the server.
|
|
||||||
func (c *Client) handleRoomListResponse(payload json.RawMessage) {
|
func (c *Client) handleRoomListResponse(payload json.RawMessage) {
|
||||||
var roomList network.RoomListResponse
|
var roomList network.RoomListResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &roomList); err == nil {
|
if err := json.Unmarshal(payload, &roomList); err == nil {
|
||||||
fmt.Println("\nAvailable rooms:")
|
fmt.Println("\nAvailable rooms:")
|
||||||
|
// Если список комнат пуст
|
||||||
if len(roomList.Rooms) == 0 {
|
if len(roomList.Rooms) == 0 {
|
||||||
fmt.Println("No rooms available.")
|
fmt.Println("No rooms available.")
|
||||||
} else {
|
} else { // Иначе выводим список комнат
|
||||||
for _, room := range roomList.Rooms {
|
for _, room := range roomList.Rooms {
|
||||||
fmt.Printf("- %s (Board Size: %dx%d, Full: %t, Mode: %s, Difficulty: %s)\n",
|
fmt.Printf("- %s (Board Size: %dx%d, Full: %t, "+
|
||||||
|
"Mode: %s, Difficulty: %s)\n",
|
||||||
room.Name,
|
room.Name,
|
||||||
room.BoardSize, room.BoardSize,
|
room.BoardSize, room.BoardSize,
|
||||||
room.IsFull,
|
room.IsFull,
|
||||||
@@ -146,37 +133,39 @@ func (c *Client) handleRoomListResponse(payload json.RawMessage) {
|
|||||||
c.setState(mainMenu)
|
c.setState(mainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleNickNameResponse processes the NickNameResponse message from the server.
|
// Обрабатываем ответ на запрос на присоединение к комнате
|
||||||
func (c *Client) handleNickNameResponse(payload json.RawMessage) {
|
func (c *Client) handleNickNameResponse(payload json.RawMessage) {
|
||||||
var res network.NickNameResponse
|
var res network.NickNameResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
fmt.Printf("\nWelcome, %s!\n> ", res.Nickname)
|
fmt.Printf("\nWelcome, %s!\n> ", res.Nickname)
|
||||||
c.setNickname(res.Nickname)
|
c.setNickname(res.Nickname) // Устанавливаем никнейм игрока
|
||||||
c.setState(mainMenu)
|
c.setState(mainMenu) // Устанавливаем состояние клиента
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error unmarshalling NickNameResponse: %v", err)
|
log.Printf("Error unmarshalling NickNameResponse: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleOpponentLeft processes the OpponentLeft message from the server.
|
// Обрабатываем сообщение об отключении оппонента
|
||||||
func (c *Client) handleOpponentLeft(payload json.RawMessage) {
|
func (c *Client) handleOpponentLeft(payload json.RawMessage) {
|
||||||
var res network.OpponentLeft
|
var res network.OpponentLeft // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
fmt.Printf("\nPlayer '%s' has left the game.\n> ", res.Nickname)
|
fmt.Printf("\nPlayer '%s' has left the game.\n> ", res.Nickname)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Error unmarshalling OpponentLeft: %v", err)
|
log.Printf("Error unmarshalling OpponentLeft: %v", err)
|
||||||
}
|
}
|
||||||
|
// Устанавливаем состояние клиента
|
||||||
c.setState(waitingOpponentInRoom)
|
c.setState(waitingOpponentInRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleFinishedGamesResponse processes the FinishedGamesResponse message from the server.
|
// Обрабатываем ответ на запрос на получение списка завершенных игр
|
||||||
func (c *Client) handleFinishedGamesResponse(payload json.RawMessage) {
|
func (c *Client) handleFinishedGamesResponse(payload json.RawMessage) {
|
||||||
var res network.FinishedGamesResponse
|
var res network.FinishedGamesResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
fmt.Println("\nFinished games:")
|
fmt.Println("\nFinished games:")
|
||||||
|
// Если список завершенных игр пуст
|
||||||
if res.Games == nil || len(*res.Games) == 0 {
|
if res.Games == nil || len(*res.Games) == 0 {
|
||||||
fmt.Println("No finished games.")
|
fmt.Println("No finished games.")
|
||||||
} else {
|
} else { // Иначе выводим список завершенных игр
|
||||||
for _, game := range *res.Games {
|
for _, game := range *res.Games {
|
||||||
fmt.Printf("- Game #%d: %s vs %s (Winner: %s) at %v\n",
|
fmt.Printf("- Game #%d: %s vs %s (Winner: %s) at %v\n",
|
||||||
game.ID, game.WinnerName,
|
game.ID, game.WinnerName,
|
||||||
@@ -188,13 +177,16 @@ func (c *Client) handleFinishedGamesResponse(payload json.RawMessage) {
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("Error unmarshalling FinishedGamesResponse: %v", err)
|
log.Printf("Error unmarshalling FinishedGamesResponse: %v", err)
|
||||||
}
|
}
|
||||||
|
// Устанавливаем состояние клиента
|
||||||
c.setState(mainMenu)
|
c.setState(mainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleFinishedGameResponse processes the FinishedGameResponse message from the server.
|
// Обрабатываем ответ на запрос на получение данных
|
||||||
|
// о конкретной завершенной игре
|
||||||
func (c *Client) handleFinishedGameResponse(payload json.RawMessage) {
|
func (c *Client) handleFinishedGameResponse(payload json.RawMessage) {
|
||||||
var res network.FinishedGameResponse
|
var res network.FinishedGameResponse // Десериализуем ответ
|
||||||
if err := json.Unmarshal(payload, &res); err == nil {
|
if err := json.Unmarshal(payload, &res); err == nil {
|
||||||
|
// Выводим информацию о завершенной игре
|
||||||
fmt.Println("\nFinished game:")
|
fmt.Println("\nFinished game:")
|
||||||
fmt.Printf("- Game #%d: %s vs %s (Winner: %s) at %v\n",
|
fmt.Printf("- Game #%d: %s vs %s (Winner: %s) at %v\n",
|
||||||
res.Game.ID, res.Game.WinnerName,
|
res.Game.ID, res.Game.WinnerName,
|
||||||
@@ -207,5 +199,5 @@ func (c *Client) handleFinishedGameResponse(payload json.RawMessage) {
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("Error unmarshalling FinishedGameResponse: %v", err)
|
log.Printf("Error unmarshalling FinishedGameResponse: %v", err)
|
||||||
}
|
}
|
||||||
c.setState(mainMenu)
|
c.setState(mainMenu) // Устанавливаем состояние клиента
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,36 +3,69 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
b "tic-tac-toe/board"
|
b "tic-tac-toe/board"
|
||||||
|
g "tic-tac-toe/game"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Выводит информацию о ходе игрока
|
||||||
func (c *Client) printTurnInfo() {
|
func (c *Client) printTurnInfo() {
|
||||||
if c.board == nil {
|
if c.board == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.currentPlayer == c.mySymbol {
|
if c.currentPlayer == c.mySymbol { // Если ход игрока
|
||||||
fmt.Println("It's your turn.")
|
fmt.Println("It's your turn.")
|
||||||
} else if c.currentPlayer != b.Empty {
|
} else if c.currentPlayer != b.Empty { // Если ход оппонента
|
||||||
fmt.Printf("It's player %s's turn.\n", c.currentPlayer)
|
fmt.Printf("It's player %s's turn.\n", c.currentPlayer)
|
||||||
} else {
|
} else {
|
||||||
// Game might be over or in an intermediate state
|
// Игра может быть завершена или находиться
|
||||||
|
// в промежуточном состоянии
|
||||||
}
|
}
|
||||||
fmt.Print("> ")
|
fmt.Print("> ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateMove checks if a move is valid based on the local board state.
|
// Проверяем валидность хода игрока
|
||||||
func (c *Client) validateMove(row, col int) bool {
|
func (c *Client) validateMove(row, col int) bool {
|
||||||
if c.board == nil {
|
if c.board == nil { // Если игра не начата
|
||||||
fmt.Println("Game has not started yet.")
|
fmt.Println("Game has not started yet.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// Если ход вне поля
|
||||||
if row < 1 || row > c.board.Size || col < 1 || col > c.board.Size {
|
if row < 1 || row > c.board.Size || col < 1 || col > c.board.Size {
|
||||||
fmt.Printf("Invalid move. Row and column must be between 1 and %d.\n", c.board.Size)
|
fmt.Printf(
|
||||||
|
"Invalid move. Row and column must be between 1 and %d.\n",
|
||||||
|
c.board.Size,
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Convert to 0-indexed for board access
|
// Преобразуем в 0-индексированный для доступа к полю
|
||||||
if c.board.Board[row-1][col-1] != b.Empty {
|
if c.board.Board[row-1][col-1] != b.Empty {
|
||||||
fmt.Println("Invalid move. Cell is already occupied.")
|
fmt.Println("Invalid move. Cell is already occupied.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Конвертируем экземпляр типа GameMode в строку
|
||||||
|
func gameModeToString(mode g.GameMode) string {
|
||||||
|
switch mode {
|
||||||
|
case g.PvP:
|
||||||
|
return "PvP"
|
||||||
|
case g.PvC:
|
||||||
|
return "PvC"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конвертируем экземпляр типа Difficulty в строку
|
||||||
|
func difficultyToString(difficulty g.Difficulty) string {
|
||||||
|
switch difficulty {
|
||||||
|
case g.Easy:
|
||||||
|
return "Easy"
|
||||||
|
case g.Medium:
|
||||||
|
return "Medium"
|
||||||
|
case g.Hard:
|
||||||
|
return "Hard"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const (
|
|||||||
CmdUpdateState Command = "update_state"
|
CmdUpdateState Command = "update_state"
|
||||||
CmdError Command = "error"
|
CmdError Command = "error"
|
||||||
CmdNickNameResponse Command = "nick_name_response"
|
CmdNickNameResponse Command = "nick_name_response"
|
||||||
CmdRoomCreated Command = "room_created"
|
|
||||||
CmdRoomJoinResponse Command = "room_join_response"
|
CmdRoomJoinResponse Command = "room_join_response"
|
||||||
CmdRoomListResponse Command = "room_list_response"
|
CmdRoomListResponse Command = "room_list_response"
|
||||||
CmdInitGame Command = "init_game"
|
CmdInitGame Command = "init_game"
|
||||||
@@ -69,12 +68,6 @@ type NickNameResponse struct {
|
|||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляется сервером после успешного создания комнаты
|
|
||||||
type RoomCreatedResponse struct {
|
|
||||||
RoomID string `json:"room_id"`
|
|
||||||
RoomName string `json:"room_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправляется сервером, когда клиент успешно присоединился к комнате
|
// Отправляется сервером, когда клиент успешно присоединился к комнате
|
||||||
type RoomJoinResponse struct {
|
type RoomJoinResponse struct {
|
||||||
RoomName string `json:"room_name"`
|
RoomName string `json:"room_name"`
|
||||||
|
|||||||
@@ -13,21 +13,25 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Room manages the state of a single game room.
|
// Room — структура, которая описывает игровую комнату
|
||||||
type Room struct {
|
type Room struct {
|
||||||
Name string
|
Name string // Название комнаты
|
||||||
Board *b.Board
|
Board *b.Board // Ссылка на игровую доску
|
||||||
Player1 p.IPlayer
|
Player1 p.IPlayer // Первый игрок (только человек)
|
||||||
|
Mode g.GameMode // Режим игры: PvP или PvC
|
||||||
|
|
||||||
|
// Второй игрок (может быть человеком или компьютером)
|
||||||
Player2 p.IPlayer
|
Player2 p.IPlayer
|
||||||
|
// Текущий игрок, который должен сделать ход
|
||||||
CurrentPlayer p.IPlayer
|
CurrentPlayer p.IPlayer
|
||||||
|
// Текущее состояние игры (чей ход, победа, ничья и т.д.)
|
||||||
State g.GameState
|
State g.GameState
|
||||||
repository db.IRepository
|
// Уровень сложности компьютера (используется только в режиме PvC)
|
||||||
Mode g.GameMode
|
|
||||||
// Уровень сложности компьютера (только для PvC)
|
|
||||||
Difficulty g.Difficulty
|
Difficulty g.Difficulty
|
||||||
|
// Интерфейс для сохранения завершенных игр в базе данных
|
||||||
|
repository db.IRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRoom creates a new game room.
|
|
||||||
func NewRoom(
|
func NewRoom(
|
||||||
name string, repository db.IRepository, boardSize int,
|
name string, repository db.IRepository, boardSize int,
|
||||||
gameMode g.GameMode, difficulty g.Difficulty,
|
gameMode g.GameMode, difficulty g.Difficulty,
|
||||||
@@ -40,61 +44,83 @@ func NewRoom(
|
|||||||
Board: b.NewBoard(boardSize),
|
Board: b.NewBoard(boardSize),
|
||||||
State: g.WaitingOpponent,
|
State: g.WaitingOpponent,
|
||||||
}
|
}
|
||||||
|
// Если режим игры — PvC, то создаем компьютерного игрока
|
||||||
if gameMode == g.PvC {
|
if gameMode == g.PvC {
|
||||||
room.Player2 = p.NewComputerPlayer(b.Nought, difficulty)
|
room.Player2 = p.NewComputerPlayer(b.Nought, difficulty)
|
||||||
}
|
}
|
||||||
return room
|
return room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Возвращает true, если в комнате есть два игрока
|
||||||
func (r *Room) IsFull() bool {
|
func (r *Room) IsFull() bool {
|
||||||
return r.Player1 != nil && r.Player2 != nil
|
return r.Player1 != nil && r.Player2 != nil // Если оба игрока не равны nil, значит комната полная
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Возвращает количество игроков в комнате
|
||||||
func (r *Room) PlayersAmount() int {
|
func (r *Room) PlayersAmount() int {
|
||||||
if r.Player1 != nil && r.Player2 != nil {
|
if r.Player1 != nil && r.Player2 != nil {
|
||||||
return 2
|
return 2 // Если оба игрока есть, возвращаем 2
|
||||||
|
} else if r.Player1 != nil || r.Player2 != nil {
|
||||||
|
return 1 // Если только один игрок, возвращаем 1
|
||||||
}
|
}
|
||||||
return 1
|
return 0 // Если ни один игрок не добавлен, возвращаем 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Возвращает размер доски
|
||||||
func (r *Room) BoardSize() int {
|
func (r *Room) BoardSize() int {
|
||||||
return r.Board.Size
|
return r.Board.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем игрока в комнату
|
||||||
|
// Первый добавленный игрок становится Player1, второй — Player2
|
||||||
|
// Символы игроков автоматически корректируются: 1 — X, 2 — O
|
||||||
func (r *Room) AddPlayer(player p.IPlayer) {
|
func (r *Room) AddPlayer(player p.IPlayer) {
|
||||||
if r.Player1 == nil {
|
if r.Player1 == nil {
|
||||||
r.Player1 = player
|
r.Player1 = player // Первый игрок
|
||||||
if r.Player1.GetSymbol() != "X" {
|
if r.Player1.GetSymbol() != "X" {
|
||||||
r.Player1.SwitchPlayer()
|
r.Player1.SwitchPlayer() // Если символ не X, меняем на X
|
||||||
}
|
}
|
||||||
} else if r.Player2 == nil {
|
} else if r.Player2 == nil {
|
||||||
r.Player2 = player
|
r.Player2 = player // Второй игрок
|
||||||
if r.Player2.GetSymbol() != "O" {
|
if r.Player2.GetSymbol() != "O" {
|
||||||
r.Player2.SwitchPlayer()
|
r.Player2.SwitchPlayer() // Если символ не O, меняем на O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Удаляем игрока из комнаты
|
||||||
|
// В случае выхода игрока его оппоненту (если присутствует в
|
||||||
|
// комнате и человек) отправляется сообщение о выходе соперника
|
||||||
func (r *Room) RemovePlayer(player p.IPlayer) {
|
func (r *Room) RemovePlayer(player p.IPlayer) {
|
||||||
if r.Player1 == player {
|
if r.Player1 == player {
|
||||||
r.Player1 = nil
|
r.Player1 = nil // Удаляем первого игрока
|
||||||
|
// Если в комнате есть второй игрок и он человек,
|
||||||
|
// уведомляем его о выходе соперника
|
||||||
if r.Player2 != nil && !r.Player2.IsComputer() {
|
if r.Player2 != nil && !r.Player2.IsComputer() {
|
||||||
opponentLeft := &n.OpponentLeft{Nickname: player.GetNickname()}
|
opponentLeft := &n.OpponentLeft{
|
||||||
|
// Имя вышедшего игрока
|
||||||
|
Nickname: player.GetNickname(),
|
||||||
|
}
|
||||||
payloadBytes, err := json.Marshal(opponentLeft)
|
payloadBytes, err := json.Marshal(opponentLeft)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshaling OpponentLeft: %v", err)
|
log.Printf("Error marshaling OpponentLeft: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg := &n.Message{
|
msg := &n.Message{
|
||||||
Cmd: n.CmdOpponentLeft,
|
Cmd: n.CmdOpponentLeft, // Команда "соперник вышел"
|
||||||
Payload: payloadBytes,
|
Payload: payloadBytes,
|
||||||
}
|
}
|
||||||
|
// Отправляем сообщение второму игроку
|
||||||
r.Player2.SendMessage(msg)
|
r.Player2.SendMessage(msg)
|
||||||
}
|
}
|
||||||
} else if r.Player2 == player {
|
} else if r.Player2 == player {
|
||||||
r.Player2 = nil
|
r.Player2 = nil // Удаляем второго игрока
|
||||||
if r.Player1 != nil && !r.Player1.IsComputer() {
|
// Если в комнате есть первый игрок,
|
||||||
opponentLeft := &n.OpponentLeft{Nickname: player.GetNickname()}
|
// уведомляем его о выходе соперника
|
||||||
|
if r.Player1 != nil {
|
||||||
|
opponentLeft := &n.OpponentLeft{
|
||||||
|
Nickname: player.GetNickname(),
|
||||||
|
}
|
||||||
payloadBytes, err := json.Marshal(opponentLeft)
|
payloadBytes, err := json.Marshal(opponentLeft)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshaling OpponentLeft: %v", err)
|
log.Printf("Error marshaling OpponentLeft: %v", err)
|
||||||
@@ -104,26 +130,33 @@ func (r *Room) RemovePlayer(player p.IPlayer) {
|
|||||||
Cmd: n.CmdOpponentLeft,
|
Cmd: n.CmdOpponentLeft,
|
||||||
Payload: payloadBytes,
|
Payload: payloadBytes,
|
||||||
}
|
}
|
||||||
|
// Отправляем сообщение первому игроку
|
||||||
r.Player1.SendMessage(msg)
|
r.Player1.SendMessage(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Инициализируем новую игру в комнате.
|
||||||
|
// Здесь выбирается случайный игрок, который начинает первым,
|
||||||
|
// и отправляется сообщение обоим игрокам о начале игры.
|
||||||
|
// Если первый ход за компьютером, то он делает ход автоматически
|
||||||
func (r *Room) InitGame() {
|
func (r *Room) InitGame() {
|
||||||
if !r.IsFull() {
|
if !r.IsFull() {
|
||||||
return
|
return // Если игроков меньше двух, игра не начинается
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Срез для определения игрока, ходящего первым
|
||||||
randomPlayer := []b.BoardField{b.Cross, b.Nought}
|
randomPlayer := []b.BoardField{b.Cross, b.Nought}
|
||||||
if !r.Board.IsEmpty() {
|
if !r.Board.IsEmpty() {
|
||||||
|
// Если доска не пуста, создаем новую
|
||||||
r.Board = b.NewBoard(r.Board.Size)
|
r.Board = b.NewBoard(r.Board.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := &n.Message{Cmd: n.CmdInitGame}
|
msg := &n.Message{Cmd: n.CmdInitGame} // Сообщение о начале игры
|
||||||
initGamePayload := &n.InitGameResponse{
|
initGamePayload := &n.InitGameResponse{
|
||||||
Board: *r.Board,
|
Board: *r.Board, // Текущее состояние доски
|
||||||
}
|
}
|
||||||
// Select a random starting symbol
|
// Выбираем случайным образом, кто ходит первым (X или O)
|
||||||
starterSymbol := randomPlayer[rand.Intn(len(randomPlayer))]
|
starterSymbol := randomPlayer[rand.Intn(len(randomPlayer))]
|
||||||
switch starterSymbol {
|
switch starterSymbol {
|
||||||
case b.Cross:
|
case b.Cross:
|
||||||
@@ -134,33 +167,40 @@ func (r *Room) InitGame() {
|
|||||||
initGamePayload.CurrentPlayer = b.Nought
|
initGamePayload.CurrentPlayer = b.Nought
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current player based on game mode and starter symbol
|
// Устанавливаем активного игрока в зависимости
|
||||||
|
// от режима игры и символа
|
||||||
if r.Mode == g.PvC {
|
if r.Mode == g.PvC {
|
||||||
// In PvC mode, Player1 is always the human player
|
// В режиме PvC человек всегда Player1
|
||||||
if r.State == g.CrossStep {
|
if r.State == g.CrossStep {
|
||||||
r.CurrentPlayer = r.Player1
|
r.CurrentPlayer = r.Player1
|
||||||
} else if r.State == g.NoughtStep {
|
} else if r.State == g.NoughtStep {
|
||||||
r.CurrentPlayer = r.Player2
|
r.CurrentPlayer = r.Player2
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In PvP mode, set the current player based on who has the starter symbol
|
// В режиме PvP ищем, кто играет выбранным символом
|
||||||
if (r.State == g.CrossStep && r.Player1.GetFigure() == b.Cross) ||
|
if (r.State == g.CrossStep &&
|
||||||
(r.State == g.NoughtStep && r.Player1.GetFigure() == b.Nought) {
|
r.Player1.GetFigure() == b.Cross) ||
|
||||||
|
(r.State == g.NoughtStep &&
|
||||||
|
r.Player1.GetFigure() == b.Nought) {
|
||||||
r.CurrentPlayer = r.Player1
|
r.CurrentPlayer = r.Player1
|
||||||
} else {
|
} else {
|
||||||
r.CurrentPlayer = r.Player2
|
r.CurrentPlayer = r.Player2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Сериализуем данные для отправки игрокам
|
||||||
payloadBytes, err := json.Marshal(initGamePayload)
|
payloadBytes, err := json.Marshal(initGamePayload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshaling InitGameResponse for Player1 after Player2 left: %v", err)
|
log.Printf(
|
||||||
|
"Error marshaling InitGameResponse for Player1 "+
|
||||||
|
"after Player2 left: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg.Payload = payloadBytes
|
msg.Payload = payloadBytes
|
||||||
r.Player1.SendMessage(msg)
|
r.Player1.SendMessage(msg) // Отправляем сообщение первому игроку
|
||||||
r.Player2.SendMessage(msg)
|
r.Player2.SendMessage(msg) // Отправляем сообщение второму игроку
|
||||||
|
|
||||||
|
// Если сейчас ход компьютера, он делает ход автоматически
|
||||||
if r.CurrentPlayer.IsComputer() {
|
if r.CurrentPlayer.IsComputer() {
|
||||||
row, col, _ := r.CurrentPlayer.MakeMove(r.Board)
|
row, col, _ := r.CurrentPlayer.MakeMove(r.Board)
|
||||||
r.PlayerStep(r.CurrentPlayer, row, col)
|
r.PlayerStep(r.CurrentPlayer, row, col)
|
||||||
@@ -170,29 +210,38 @@ func (r *Room) InitGame() {
|
|||||||
// Переключаем активного игрока
|
// Переключаем активного игрока
|
||||||
func (r *Room) switchCurrentPlayer() {
|
func (r *Room) switchCurrentPlayer() {
|
||||||
if r.CurrentPlayer == r.Player1 {
|
if r.CurrentPlayer == r.Player1 {
|
||||||
r.CurrentPlayer = r.Player2
|
r.CurrentPlayer = r.Player2 // Если сейчас ходил первый, теперь ход второго
|
||||||
} else {
|
} else {
|
||||||
r.CurrentPlayer = r.Player1
|
r.CurrentPlayer = r.Player1 // И наоборот
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PlayerStep выполняет ход игрока и обновляет состояние игры
|
||||||
|
// Здесь проверяется правильность хода, обновляется доска,
|
||||||
|
// определяется победитель или ничья, и отправляются сообщения игрокам.
|
||||||
|
// Если после хода игра не закончена, ход переходит следующему игроку.
|
||||||
|
// Если ходит компьютер — он делает ход автоматически.
|
||||||
func (r *Room) PlayerStep(player p.IPlayer, row, col int) {
|
func (r *Room) PlayerStep(player p.IPlayer, row, col int) {
|
||||||
msg := &n.Message{}
|
msg := &n.Message{} // Создаем новое сообщение для игроков
|
||||||
|
// Проверяем, что сейчас идет ход (игра не завершена)
|
||||||
if r.State != g.CrossStep && r.State != g.NoughtStep {
|
if r.State != g.CrossStep && r.State != g.NoughtStep {
|
||||||
return
|
return // Если сейчас не ход, ничего не делаем
|
||||||
}
|
}
|
||||||
// проверяем, что ход делает текущий игрок
|
// Проверяем, что ход делает именно тот игрок, чей сейчас ход
|
||||||
if player != r.CurrentPlayer {
|
if player != r.CurrentPlayer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ставим символ игрока на выбранную клетку
|
||||||
r.Board.SetSymbol(row, col, r.CurrentPlayer.GetFigure())
|
r.Board.SetSymbol(row, col, r.CurrentPlayer.GetFigure())
|
||||||
|
// Проверяем, выиграл ли этот игрок
|
||||||
if r.Board.CheckWin(r.CurrentPlayer.GetFigure()) {
|
if r.Board.CheckWin(r.CurrentPlayer.GetFigure()) {
|
||||||
if r.CurrentPlayer.GetFigure() == b.Cross {
|
if r.CurrentPlayer.GetFigure() == b.Cross {
|
||||||
r.State = g.CrossWin
|
r.State = g.CrossWin // Победа крестиков
|
||||||
} else {
|
} else {
|
||||||
r.State = g.NoughtWin
|
r.State = g.NoughtWin // Победа ноликов
|
||||||
}
|
}
|
||||||
|
// Формируем сообщение о завершении игры
|
||||||
msg.Cmd = n.CmdEndGame
|
msg.Cmd = n.CmdEndGame
|
||||||
endGamePayload := &n.EndGameResponse{
|
endGamePayload := &n.EndGameResponse{
|
||||||
Board: *r.Board,
|
Board: *r.Board,
|
||||||
@@ -200,6 +249,7 @@ func (r *Room) PlayerStep(player p.IPlayer, row, col int) {
|
|||||||
}
|
}
|
||||||
msg.Payload, _ = json.Marshal(endGamePayload)
|
msg.Payload, _ = json.Marshal(endGamePayload)
|
||||||
|
|
||||||
|
// Сохраняем информацию о завершенной игре в базе данных
|
||||||
figureWinner := r.CurrentPlayer.GetFigure()
|
figureWinner := r.CurrentPlayer.GetFigure()
|
||||||
winnerNickName := r.CurrentPlayer.GetNickname()
|
winnerNickName := r.CurrentPlayer.GetNickname()
|
||||||
|
|
||||||
@@ -217,6 +267,7 @@ func (r *Room) PlayerStep(player p.IPlayer, row, col int) {
|
|||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
})
|
})
|
||||||
} else if r.Board.CheckDraw() {
|
} else if r.Board.CheckDraw() {
|
||||||
|
// Если доска заполнена, но победителя нет — ничья
|
||||||
r.State = g.Draw
|
r.State = g.Draw
|
||||||
msg.Cmd = n.CmdEndGame
|
msg.Cmd = n.CmdEndGame
|
||||||
endGamePayload := &n.EndGameResponse{
|
endGamePayload := &n.EndGameResponse{
|
||||||
@@ -225,13 +276,14 @@ func (r *Room) PlayerStep(player p.IPlayer, row, col int) {
|
|||||||
}
|
}
|
||||||
msg.Payload, _ = json.Marshal(endGamePayload)
|
msg.Payload, _ = json.Marshal(endGamePayload)
|
||||||
} else {
|
} else {
|
||||||
|
// Если игра не закончена, меняем активного игрока и продолжаем
|
||||||
if r.CurrentPlayer.GetFigure() == b.Cross {
|
if r.CurrentPlayer.GetFigure() == b.Cross {
|
||||||
r.State = g.NoughtStep
|
r.State = g.NoughtStep
|
||||||
} else {
|
} else {
|
||||||
r.State = g.CrossStep
|
r.State = g.CrossStep
|
||||||
}
|
}
|
||||||
r.switchCurrentPlayer()
|
r.switchCurrentPlayer()
|
||||||
msg.Cmd = n.CmdUpdateState
|
msg.Cmd = n.CmdUpdateState // Сообщаем о новом состоянии игры
|
||||||
stateUpdatePayload := &n.GameStateUpdate{
|
stateUpdatePayload := &n.GameStateUpdate{
|
||||||
Board: *r.Board,
|
Board: *r.Board,
|
||||||
CurrentPlayer: r.CurrentPlayer.GetFigure(),
|
CurrentPlayer: r.CurrentPlayer.GetFigure(),
|
||||||
@@ -239,15 +291,20 @@ func (r *Room) PlayerStep(player p.IPlayer, row, col int) {
|
|||||||
msg.Payload, _ = json.Marshal(stateUpdatePayload)
|
msg.Payload, _ = json.Marshal(stateUpdatePayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Отправляем сообщение обоим игрокам
|
||||||
r.Player1.SendMessage(msg)
|
r.Player1.SendMessage(msg)
|
||||||
r.Player2.SendMessage(msg)
|
r.Player2.SendMessage(msg)
|
||||||
|
|
||||||
if r.State == g.CrossWin || r.State == g.NoughtWin || r.State == g.Draw {
|
// Если игра завершена (победа или ничья),
|
||||||
|
// ждем 10 секунд и запускаем новую
|
||||||
|
if r.State == g.CrossWin || r.State == g.NoughtWin ||
|
||||||
|
r.State == g.Draw {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
r.InitGame()
|
r.InitGame()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если теперь ход компьютера, он делает ход автоматически
|
||||||
if r.CurrentPlayer.IsComputer() {
|
if r.CurrentPlayer.IsComputer() {
|
||||||
row, col, _ := r.CurrentPlayer.MakeMove(r.Board)
|
row, col, _ := r.CurrentPlayer.MakeMove(r.Board)
|
||||||
r.PlayerStep(r.CurrentPlayer, row, col)
|
r.PlayerStep(r.CurrentPlayer, row, col)
|
||||||
|
|||||||
@@ -9,8 +9,15 @@ import (
|
|||||||
p "tic-tac-toe/player"
|
p "tic-tac-toe/player"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// счетчик игроков на случай, когда игрок ввел никнейм
|
||||||
|
// который уже занят
|
||||||
var defaultPlayerCounts int = 0
|
var defaultPlayerCounts int = 0
|
||||||
|
|
||||||
|
// Обрабатываем входящие команды от клиента.
|
||||||
|
// В зависимости от типа команды вызываем соответствующий обработчик:
|
||||||
|
// регистрации никнейма, запроса на ход, списка комнат,
|
||||||
|
// входа/выхода из комнаты, работы с завершёнными играми.
|
||||||
|
// Если команда неизвестна, пишем об этом в лог.
|
||||||
func (s *Server) handleCommand(client net.Conn, msg *network.Message) {
|
func (s *Server) handleCommand(client net.Conn, msg *network.Message) {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"Received command '%s' from %s",
|
"Received command '%s' from %s",
|
||||||
@@ -18,18 +25,25 @@ func (s *Server) handleCommand(client net.Conn, msg *network.Message) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
switch msg.Cmd {
|
switch msg.Cmd {
|
||||||
|
// Обрабатываем команду регистрации никнейма
|
||||||
case network.CmdNickname:
|
case network.CmdNickname:
|
||||||
s.nickNameHandler(client, msg)
|
s.nickNameHandler(client, msg)
|
||||||
|
// Обрабатываем команду хода игрока
|
||||||
case network.CmdMakeMoveRequest:
|
case network.CmdMakeMoveRequest:
|
||||||
s.makeMoveHandler(client, msg)
|
s.makeMoveHandler(client, msg)
|
||||||
|
// Обрабатываем команду запроса списка комнат
|
||||||
case network.CmdListRoomsRequest:
|
case network.CmdListRoomsRequest:
|
||||||
s.listRoomsHandler(client, msg)
|
s.listRoomsHandler(client, msg)
|
||||||
|
// Обрабатываем команду присоединения к комнате
|
||||||
case network.CmdJoinRoomRequest:
|
case network.CmdJoinRoomRequest:
|
||||||
s.joinRoomHandler(client, msg)
|
s.joinRoomHandler(client, msg)
|
||||||
|
// Обрабатываем команду выхода из комнаты
|
||||||
case network.CmdLeaveRoomRequest:
|
case network.CmdLeaveRoomRequest:
|
||||||
s.leaveRoomHandler(client, msg)
|
s.leaveRoomHandler(client, msg)
|
||||||
|
// Обрабатываем команду запроса списка завершенных игр
|
||||||
case network.CmdFinishedGamesRequest:
|
case network.CmdFinishedGamesRequest:
|
||||||
s.getFinishedGamesHandler(client, msg)
|
s.getFinishedGamesHandler(client, msg)
|
||||||
|
// Обрабатываем команду запроса завершенной игры по ID
|
||||||
case network.CmdFinishedGameByIdRequest:
|
case network.CmdFinishedGameByIdRequest:
|
||||||
s.getFinishedGameByIdHandler(client, msg)
|
s.getFinishedGameByIdHandler(client, msg)
|
||||||
default:
|
default:
|
||||||
@@ -37,54 +51,77 @@ func (s *Server) handleCommand(client net.Conn, msg *network.Message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем запрос на регистрацию никнейма игрока.
|
||||||
|
// Проверяем уникальность никнейма, при необходимости
|
||||||
|
// добавляем суффикс, создаем нового игрока и отправляем
|
||||||
|
// клиенту подтверждение с итоговым никнеймом.
|
||||||
func (s *Server) nickNameHandler(client net.Conn, msg *network.Message) {
|
func (s *Server) nickNameHandler(client net.Conn, msg *network.Message) {
|
||||||
|
// Десериализуем сообщение
|
||||||
nicknameRequest := &network.NicknameRequest{}
|
nicknameRequest := &network.NicknameRequest{}
|
||||||
if err := json.Unmarshal(msg.Payload, nicknameRequest); err != nil {
|
if err := json.Unmarshal(msg.Payload, nicknameRequest); err != nil {
|
||||||
log.Printf("Error unmarshaling NicknameRequest: %v", err)
|
log.Printf("Error unmarshaling NicknameRequest: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.mutex.Lock() // Защищаем доступ к данным
|
||||||
if s.players[nicknameRequest.Nickname] != nil {
|
if s.players[nicknameRequest.Nickname] != nil {
|
||||||
|
// Если никнейм занят, добавляем суффикс
|
||||||
nicknameRequest.Nickname = nicknameRequest.Nickname +
|
nicknameRequest.Nickname = nicknameRequest.Nickname +
|
||||||
"_" + strconv.Itoa(defaultPlayerCounts)
|
"_" + strconv.Itoa(defaultPlayerCounts)
|
||||||
defaultPlayerCounts++
|
defaultPlayerCounts++
|
||||||
}
|
}
|
||||||
|
// Создаем игрока и добавляем его в карту игроков
|
||||||
s.players[nicknameRequest.Nickname] = p.NewHumanPlayer(
|
s.players[nicknameRequest.Nickname] = p.NewHumanPlayer(
|
||||||
nicknameRequest.Nickname, &client,
|
nicknameRequest.Nickname, &client,
|
||||||
)
|
)
|
||||||
|
s.mutex.Unlock()
|
||||||
|
// Формируем ответ клиенту
|
||||||
response := &network.NickNameResponse{
|
response := &network.NickNameResponse{
|
||||||
Nickname: nicknameRequest.Nickname,
|
Nickname: nicknameRequest.Nickname,
|
||||||
}
|
}
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
msg.Cmd = network.CmdNickNameResponse
|
msg.Cmd = network.CmdNickNameResponse
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg) // Отправляем ответ клиенту
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем запрос на присоединение к комнате.
|
||||||
|
// Проверяем существование комнаты и игрока,
|
||||||
|
// добавляем игрока в комнату (если есть место),
|
||||||
|
// отправляем клиенту подтверждение и инициируем старт игры
|
||||||
func (s *Server) joinRoomHandler(client net.Conn, msg *network.Message) {
|
func (s *Server) joinRoomHandler(client net.Conn, msg *network.Message) {
|
||||||
|
// Десериализуем сообщение
|
||||||
joinRoomRequest := &network.JoinRoomRequest{}
|
joinRoomRequest := &network.JoinRoomRequest{}
|
||||||
if err := json.Unmarshal(msg.Payload, joinRoomRequest); err != nil {
|
if err := json.Unmarshal(msg.Payload, joinRoomRequest); err != nil {
|
||||||
log.Printf("Error unmarshaling JoinRoomRequest: %v", err)
|
log.Printf("Error unmarshaling JoinRoomRequest: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Получаем комнату и игрока
|
||||||
|
s.mutex.RLock() // Защищаем доступ к данным
|
||||||
room, okRoom := s.rooms[joinRoomRequest.RoomName]
|
room, okRoom := s.rooms[joinRoomRequest.RoomName]
|
||||||
player, okPlayer := s.players[joinRoomRequest.PlayerName]
|
player, okPlayer := s.players[joinRoomRequest.PlayerName]
|
||||||
|
s.mutex.RUnlock()
|
||||||
|
// Проверяем существование комнаты и игрока
|
||||||
if !okRoom || !okPlayer {
|
if !okRoom || !okPlayer {
|
||||||
|
// Если комнаты или игрока не существует
|
||||||
response := &network.ErrorResponse{Message: "Room not found"}
|
response := &network.ErrorResponse{Message: "Room not found"}
|
||||||
msg.Cmd = network.CmdError
|
msg.Cmd = network.CmdError
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Защищаем доступ к данным
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
if room.IsFull() {
|
if room.IsFull() { // Если комната заполнена
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
// Отправляем ответ клиенту
|
||||||
response := &network.ErrorResponse{Message: "Room is full"}
|
response := &network.ErrorResponse{Message: "Room is full"}
|
||||||
msg.Cmd = network.CmdError
|
msg.Cmd = network.CmdError
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
room.AddPlayer(player)
|
room.AddPlayer(player) // Добавляем игрока в комнату
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
// Формируем ответ клиенту
|
||||||
response := &network.RoomJoinResponse{
|
response := &network.RoomJoinResponse{
|
||||||
RoomName: joinRoomRequest.RoomName,
|
RoomName: joinRoomRequest.RoomName,
|
||||||
PlayerSymbol: player.GetFigure(),
|
PlayerSymbol: player.GetFigure(),
|
||||||
@@ -92,33 +129,49 @@ func (s *Server) joinRoomHandler(client net.Conn, msg *network.Message) {
|
|||||||
}
|
}
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
msg.Cmd = network.CmdRoomJoinResponse
|
msg.Cmd = network.CmdRoomJoinResponse
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg) // Отправляем ответ клиенту
|
||||||
room.InitGame()
|
room.InitGame() // Инициируем старт игры
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) leaveRoomHandler(client net.Conn, msg *network.Message) {
|
// Обрабатываем выход игрока из комнаты.
|
||||||
|
// Проверяем существование комнаты и игрока, удаляем игрока из комнаты.
|
||||||
|
func (s *Server) leaveRoomHandler(
|
||||||
|
client net.Conn, msg *network.Message,
|
||||||
|
) {
|
||||||
|
// Десериализуем сообщение
|
||||||
leaveRoomRequest := &network.LeaveRoomRequest{}
|
leaveRoomRequest := &network.LeaveRoomRequest{}
|
||||||
if err := json.Unmarshal(msg.Payload, leaveRoomRequest); err != nil {
|
if err := json.Unmarshal(msg.Payload, leaveRoomRequest); err != nil {
|
||||||
log.Printf("Error unmarshaling LeaveRoomRequest: %v", err)
|
log.Printf("Error unmarshaling LeaveRoomRequest: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Получаем комнату и игрока
|
||||||
|
s.mutex.RLock() // Защищаем доступ к данным
|
||||||
room, okRoom := s.rooms[leaveRoomRequest.RoomName]
|
room, okRoom := s.rooms[leaveRoomRequest.RoomName]
|
||||||
player, okPlayer := s.players[leaveRoomRequest.PlayerName]
|
player, okPlayer := s.players[leaveRoomRequest.PlayerName]
|
||||||
|
s.mutex.RUnlock()
|
||||||
|
// Проверяем существование комнаты и игрока
|
||||||
if !okRoom || !okPlayer {
|
if !okRoom || !okPlayer {
|
||||||
|
// Если комнаты или игрока не существует
|
||||||
response := &network.ErrorResponse{Message: "Room not found"}
|
response := &network.ErrorResponse{Message: "Room not found"}
|
||||||
msg.Cmd = network.CmdError
|
msg.Cmd = network.CmdError
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Защищаем доступ к данным
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
room.RemovePlayer(player)
|
room.RemovePlayer(player) // Удаляем игрока из комнаты
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) listRoomsHandler(client net.Conn, msg *network.Message) {
|
// Обрабатываем запрос на получение списка всех комнат
|
||||||
|
func (s *Server) listRoomsHandler(
|
||||||
|
client net.Conn, msg *network.Message,
|
||||||
|
) {
|
||||||
|
// Защищаем доступ к данным
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
// Создаем список комнат
|
||||||
var roomInfos []network.RoomInfo
|
var roomInfos []network.RoomInfo
|
||||||
for _, room := range s.rooms {
|
for _, room := range s.rooms {
|
||||||
roomInfos = append(roomInfos, network.RoomInfo{
|
roomInfos = append(roomInfos, network.RoomInfo{
|
||||||
@@ -129,25 +182,32 @@ func (s *Server) listRoomsHandler(client net.Conn, msg *network.Message) {
|
|||||||
Difficult: room.Difficulty,
|
Difficult: room.Difficulty,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Формируем ответ клиенту
|
||||||
response := &network.RoomListResponse{
|
response := &network.RoomListResponse{
|
||||||
Rooms: roomInfos,
|
Rooms: roomInfos,
|
||||||
}
|
}
|
||||||
msg.Cmd = network.CmdRoomListResponse
|
msg.Cmd = network.CmdRoomListResponse
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg) // Отправляем сообщение
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getFinishedGamesHandler(client net.Conn, msg *network.Message) {
|
// Обрабатываем запрос на получение списка завершенных игр
|
||||||
// получаем данные из БД
|
func (s *Server) getFinishedGamesHandler(
|
||||||
|
client net.Conn, msg *network.Message,
|
||||||
|
) {
|
||||||
|
// Получаем данные из БД
|
||||||
finishedGames, err := s.repository.GetAllFinishedGames()
|
finishedGames, err := s.repository.GetAllFinishedGames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := &network.ErrorResponse{Message: "Error getting finished games"}
|
// Если ошибка, отправляем сообщение об ошибке
|
||||||
|
response := &network.ErrorResponse{
|
||||||
|
Message: "Error getting finished games",
|
||||||
|
}
|
||||||
msg.Cmd = network.CmdError
|
msg.Cmd = network.CmdError
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Формируем ответ клиенту
|
||||||
response := &network.FinishedGamesResponse{
|
response := &network.FinishedGamesResponse{
|
||||||
Games: finishedGames,
|
Games: finishedGames,
|
||||||
}
|
}
|
||||||
@@ -156,20 +216,36 @@ func (s *Server) getFinishedGamesHandler(client net.Conn, msg *network.Message)
|
|||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getFinishedGameByIdHandler(client net.Conn, msg *network.Message) {
|
// Обрабатываем запрос на получение завершенной игры по ID
|
||||||
|
func (s *Server) getFinishedGameByIdHandler(
|
||||||
|
client net.Conn, msg *network.Message,
|
||||||
|
) {
|
||||||
|
// Десериализуем сообщение
|
||||||
getFinishedGameByIdRequest := &network.GetFinishedGameByIdRequest{}
|
getFinishedGameByIdRequest := &network.GetFinishedGameByIdRequest{}
|
||||||
if err := json.Unmarshal(msg.Payload, getFinishedGameByIdRequest); err != nil {
|
if err := json.Unmarshal(
|
||||||
log.Printf("Error unmarshaling GetFinishedGameByIdRequest: %v", err)
|
msg.Payload, getFinishedGameByIdRequest,
|
||||||
|
); err != nil {
|
||||||
|
log.Printf(
|
||||||
|
"Error unmarshaling GetFinishedGameByIdRequest: %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
finishedGame, err := s.repository.GetFinishedGameById(getFinishedGameByIdRequest.GameID)
|
// Получаем завершенную игру по ID
|
||||||
|
finishedGame, err := s.repository.GetFinishedGameById(
|
||||||
|
getFinishedGameByIdRequest.GameID,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := &network.ErrorResponse{Message: "Error getting finished game by id"}
|
// Если ошибка, отправляем сообщение об ошибке
|
||||||
|
response := &network.ErrorResponse{
|
||||||
|
Message: "Error getting finished game by id",
|
||||||
|
}
|
||||||
msg.Cmd = network.CmdError
|
msg.Cmd = network.CmdError
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Формируем ответ клиенту
|
||||||
response := &network.FinishedGameResponse{
|
response := &network.FinishedGameResponse{
|
||||||
Game: finishedGame,
|
Game: finishedGame,
|
||||||
}
|
}
|
||||||
@@ -178,21 +254,32 @@ func (s *Server) getFinishedGameByIdHandler(client net.Conn, msg *network.Messag
|
|||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) makeMoveHandler(client net.Conn, msg *network.Message) {
|
// Обрабатываем запрос на ход игрока
|
||||||
|
func (s *Server) makeMoveHandler(
|
||||||
|
client net.Conn, msg *network.Message,
|
||||||
|
) {
|
||||||
|
// Десериализуем сообщение
|
||||||
makeMoveRequest := &network.MakeMoveRequest{}
|
makeMoveRequest := &network.MakeMoveRequest{}
|
||||||
if err := json.Unmarshal(msg.Payload, makeMoveRequest); err != nil {
|
if err := json.Unmarshal(msg.Payload, makeMoveRequest); err != nil {
|
||||||
log.Printf("Error unmarshaling MakeMoveRequest: %v", err)
|
log.Printf("Error unmarshaling MakeMoveRequest: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Получаем комнату и игрока
|
||||||
|
s.mutex.RLock() // Защищаем доступ к данным
|
||||||
room, okRoom := s.rooms[makeMoveRequest.RoomName]
|
room, okRoom := s.rooms[makeMoveRequest.RoomName]
|
||||||
player, okPlayer := s.players[makeMoveRequest.PlayerName]
|
player, okPlayer := s.players[makeMoveRequest.PlayerName]
|
||||||
if !okRoom || !okPlayer {
|
s.mutex.RUnlock()
|
||||||
response := &network.ErrorResponse{Message: "Room not found"}
|
if !okRoom || !okPlayer { // Если комнаты или игрока не существует
|
||||||
|
// Отправляем сообщение об ошибке
|
||||||
|
response := &network.ErrorResponse{
|
||||||
|
Message: "Room not found",
|
||||||
|
}
|
||||||
msg.Cmd = network.CmdError
|
msg.Cmd = network.CmdError
|
||||||
msg.Payload, _ = json.Marshal(response)
|
msg.Payload, _ = json.Marshal(response)
|
||||||
json.NewEncoder(client).Encode(msg)
|
json.NewEncoder(client).Encode(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Выполняем ход игрока
|
||||||
room.PlayerStep(
|
room.PlayerStep(
|
||||||
player,
|
player,
|
||||||
makeMoveRequest.PositionRow,
|
makeMoveRequest.PositionRow,
|
||||||
|
|||||||
@@ -13,21 +13,26 @@ import (
|
|||||||
"tic-tac-toe/room"
|
"tic-tac-toe/room"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server manages client connections and game rooms.
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
// Слушатель для подключения клиентов
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
|
// Интерфейс для сохранения завершенных игр в базе данных
|
||||||
repository db.IRepository
|
repository db.IRepository
|
||||||
|
// Карта комнат
|
||||||
rooms map[string]*room.Room
|
rooms map[string]*room.Room
|
||||||
|
// Карта игроков
|
||||||
players map[string]player.IPlayer
|
players map[string]player.IPlayer
|
||||||
|
// Мьютекс для защиты доступа к данным
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates and returns a new server instance.
|
|
||||||
func NewServer(addr string, repository db.IRepository) (*Server, error) {
|
func NewServer(addr string, repository db.IRepository) (*Server, error) {
|
||||||
listener, err := net.Listen("tcp", addr)
|
listener, err := net.Listen("tcp", addr) // Создаем слушатель
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Создаем экземпляр структуры Server
|
||||||
server := &Server{
|
server := &Server{
|
||||||
listener: listener,
|
listener: listener,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
@@ -35,6 +40,8 @@ func NewServer(addr string, repository db.IRepository) (*Server, error) {
|
|||||||
players: make(map[string]player.IPlayer),
|
players: make(map[string]player.IPlayer),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Создаем комнаты и добавляем их в карту rooms по ключу,
|
||||||
|
// который является именем комнаты
|
||||||
server.rooms["room1"] = room.NewRoom(
|
server.rooms["room1"] = room.NewRoom(
|
||||||
"room1", server.repository, 3, g.PvP, g.None,
|
"room1", server.repository, 3, g.PvP, g.None,
|
||||||
)
|
)
|
||||||
@@ -48,64 +55,84 @@ func NewServer(addr string, repository db.IRepository) (*Server, error) {
|
|||||||
"room4", server.repository, 6, g.PvC, g.Hard,
|
"room4", server.repository, 6, g.PvC, g.Hard,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Возвращаем экземпляр созданного сервера
|
||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start begins listening for and handling client connections.
|
// Запускаем прослушивание подключений и обработку сообщений от клиентов
|
||||||
func (s *Server) Start() {
|
func (s *Server) Start() {
|
||||||
log.Printf("Server started, listening on %s", s.listener.Addr())
|
log.Printf("Server started, listening on %s", s.listener.Addr())
|
||||||
defer s.listener.Close()
|
defer s.listener.Close() // Закрываем слушателя при завершении
|
||||||
|
|
||||||
|
// Запускаем бесконечный цикл обработки подключений
|
||||||
for {
|
for {
|
||||||
|
// Принимаем подключение
|
||||||
conn, err := s.listener.Accept()
|
conn, err := s.listener.Accept()
|
||||||
if err != nil {
|
if err != nil { // Если ошибка, то пропускаем итерацию
|
||||||
log.Printf("Error accepting connection: %v", err)
|
log.Printf("Error accepting connection: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Чтобы не блокировать основной поток
|
||||||
|
// передаем сокетное подключение в отдельную горутину, где и
|
||||||
|
// будем обрабатывать сообщения от клиента по данному подключению
|
||||||
go s.handleConnection(conn)
|
go s.handleConnection(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleConnection manages a single client connection.
|
// Обрабатываем подключение клиента
|
||||||
func (s *Server) handleConnection(conn net.Conn) {
|
func (s *Server) handleConnection(conn net.Conn) {
|
||||||
log.Printf("New client connected: %s", conn.RemoteAddr())
|
log.Printf("New client connected: %s", conn.RemoteAddr())
|
||||||
defer conn.Close()
|
defer conn.Close() // Закрываем подключение при завершении
|
||||||
|
|
||||||
|
// Создаем декодер для чтения сообщений от клиента
|
||||||
decoder := json.NewDecoder(conn)
|
decoder := json.NewDecoder(conn)
|
||||||
for {
|
for { // Бесконечный цикл обработки сообщений
|
||||||
|
// Создаем переменную для хранения сообщения
|
||||||
var msg network.Message
|
var msg network.Message
|
||||||
if err := decoder.Decode(&msg); err != nil {
|
// Декодируем сообщение от клиента
|
||||||
|
if err := decoder.Decode(&msg); err != nil { // Если ошибка
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"Client %s disconnected: %v", conn.RemoteAddr(),
|
"Client %s disconnected: %v", conn.RemoteAddr(),
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
// Обрабатываем отключение клиента
|
||||||
s.disconnectedClientHandler(conn)
|
s.disconnectedClientHandler(conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем сообщение от клиента
|
||||||
s.handleCommand(conn, &msg)
|
s.handleCommand(conn, &msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем отключение клиента
|
||||||
func (s *Server) disconnectedClientHandler(conn net.Conn) {
|
func (s *Server) disconnectedClientHandler(conn net.Conn) {
|
||||||
var player player.IPlayer
|
var player player.IPlayer // Создаем переменную для хранения игрока
|
||||||
|
// Проходим по всем комнатам
|
||||||
for _, room := range s.rooms {
|
for _, room := range s.rooms {
|
||||||
|
// Если игрок 1 в комнате
|
||||||
if room.Player1 != nil {
|
if room.Player1 != nil {
|
||||||
|
// Если игрок 1 подключился по этому сокету
|
||||||
if room.Player1.CheckSocket(conn) {
|
if room.Player1.CheckSocket(conn) {
|
||||||
player = room.Player1
|
player = room.Player1
|
||||||
|
// Удаляем игрока из комнаты
|
||||||
room.RemovePlayer(room.Player1)
|
room.RemovePlayer(room.Player1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Если игрок 2 в комнате
|
||||||
if room.Player2 != nil {
|
if room.Player2 != nil {
|
||||||
|
// Если игрок 2 подключился по этому сокету
|
||||||
if room.Player2.CheckSocket(conn) {
|
if room.Player2.CheckSocket(conn) {
|
||||||
player = room.Player2
|
player = room.Player2
|
||||||
|
// Удаляем игрока из комнаты
|
||||||
room.RemovePlayer(room.Player2)
|
room.RemovePlayer(room.Player2)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Если игрок не найден
|
||||||
if player == nil {
|
if player == nil {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"Client %s disconnected: player not found",
|
"Client %s disconnected: player not found",
|
||||||
@@ -113,6 +140,7 @@ func (s *Server) disconnectedClientHandler(conn net.Conn) {
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Удаляем игрока из карты, предварительно защищая доступ к ней
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
delete(s.players, player.GetNickname())
|
delete(s.players, player.GetNickname())
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|||||||
Reference in New Issue
Block a user