1
0
mirror of https://github.com/MADTeacher/go_basics.git synced 2025-11-23 21:34:47 +02:00

рефакторинг игры

This commit is contained in:
Stanislav Chernyshev
2025-06-23 17:12:57 +03:00
parent d4e7211d1d
commit d0f3bff57d
11 changed files with 518 additions and 230 deletions

View File

@@ -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 " "
}
}

View File

@@ -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> ",

View File

@@ -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
) )

View File

@@ -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
} }

View File

@@ -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)
} }

View File

@@ -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) // Устанавливаем состояние клиента
} }

View File

@@ -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 ""
}
}

View File

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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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()