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

Глава 8

Допричесать игру
This commit is contained in:
Stanislav Chernyshev
2025-06-22 21:04:23 +03:00
parent b895709c86
commit d4e7211d1d
41 changed files with 1724 additions and 143 deletions

View File

@@ -0,0 +1,46 @@
package client
import (
"fmt"
"log"
"net"
)
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func RunClient(port int) {
s, _ := net.ResolveUDPAddr("udp4", fmt.Sprintf("localhost:%d", port))
connection, err := net.DialUDP("udp4", nil, s)
checkErr(err)
fmt.Printf("The UDP server is %s\n", connection.RemoteAddr().String())
defer connection.Close()
count := 0
for {
data := []byte(fmt.Sprintf("%d)Hello!\n", count))
fmt.Print("->", string(data))
_, err = connection.Write(data)
checkErr(err)
buffer := make([]byte, 1024)
n, _, err := connection.ReadFromUDP(buffer)
checkErr(err)
fmt.Printf("<-: %s", string(buffer[0:n]))
count++
if count >= 5 {
data := []byte("STOP")
fmt.Println("->", string(data))
_, err = connection.Write(data)
checkErr(err)
fmt.Println("Finished!")
return
}
}
}

3
part_8/8.1/udp/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module udp
go 1.24

38
part_8/8.1/udp/main.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"fmt"
"log"
"os"
"strconv"
"udp/client"
"udp/server"
)
const PORT = 8081
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("You didn't select a launch option!!!")
return
}
numGR, err := strconv.Atoi(os.Args[1])
checkErr(err)
switch numGR {
case 1:
fmt.Println("Server is running")
server.RunServer(PORT)
case 2:
fmt.Println("Client is running")
client.RunClient(PORT)
default:
log.Fatal("What pokemon is this?")
}
}

View File

@@ -0,0 +1,44 @@
package server
import (
"fmt"
"log"
"net"
"strings"
"time"
)
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func RunServer(port int) {
s, err := net.ResolveUDPAddr("udp4", fmt.Sprintf(":%d", port))
checkErr(err)
connection, err := net.ListenUDP("udp4", s)
checkErr(err)
defer connection.Close()
buffer := make([]byte, 1024)
fmt.Println("Oo")
for {
n, addr, err := connection.ReadFromUDP(buffer) // ждем подсоединение клиента
checkErr(err)
fmt.Printf("<- %s", string(buffer[0:n]))
if strings.TrimSpace(string(buffer[0:n])) == "STOP" {
fmt.Println()
fmt.Println("Exiting UDP server!")
return
}
time.Sleep(5 * time.Second)
fmt.Printf("->: %s", string(buffer))
_, err = connection.WriteToUDP(buffer, addr)
checkErr(err)
}
}

View File

@@ -0,0 +1,53 @@
package client
import (
"bufio"
"fmt"
"log"
"net"
)
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func RunClient(port int) {
// запускает реализацию клиента TCP и соединяет вас с нужным TCP-сервером
connection, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
checkErr(err)
fmt.Printf("The TCP server is %s\n", connection.RemoteAddr().String())
defer connection.Close()
count := 0
for {
// отправка сообщения на сервер
data := []byte(fmt.Sprintf("%d)Hello!\n", count))
fmt.Print("->", string(data))
fmt.Fprint(connection, fmt.Sprintf("%d)Hello!\n", count))
// или
//_, err = connection.Write(data)
// checkErr(err)
// считываем ответ TCP-сервера
message, err := bufio.NewReader(connection).ReadString('\n')
// или
// buffer := make([]byte, 1024)
// _, err := connection.Read(buffer)
// message := string(buffer)
checkErr(err)
fmt.Printf("<-: %s", message)
count++
if count >= 5 {
data := []byte("STOP")
fmt.Println("->", string(data))
_, err = connection.Write(data)
checkErr(err)
fmt.Println("Finished!")
return
}
}
}

3
part_8/8.2/tcp/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module udp/server
go 1.24

38
part_8/8.2/tcp/main.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"fmt"
"log"
"os"
"strconv"
"udp/server/client"
"udp/server/server"
)
const PORT = 8081
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("You didn't select a launch option!!!")
return
}
numGR, err := strconv.Atoi(os.Args[1])
checkErr(err)
switch numGR {
case 1:
fmt.Println("Server is running")
server.RunServer(PORT)
case 2:
fmt.Println("Client is running")
client.RunClient(PORT)
default:
log.Fatal("What pokemon is this?")
}
}

View File

@@ -0,0 +1,62 @@
package server
import (
"fmt"
"log"
"net"
"strings"
"time"
)
func checkErr(err error) {
if err != nil {
log.Print(err)
}
}
func handleConnection(connection net.Conn) {
defer connection.Close()
buffer := make([]byte, 1024)
for {
n, err := connection.Read(buffer) // считывание данных
checkErr(err)
fmt.Printf("Receive: %s from %s\n",
string(buffer[0:n-1]), connection.RemoteAddr().String())
if strings.TrimSpace(string(buffer[0:n])) == "STOP" {
// завершение работы сервера
fmt.Println()
fmt.Printf("Close connection with client: %s\n",
connection.RemoteAddr().String())
break
}
time.Sleep(2 * time.Second)
fmt.Printf("Send : %s to %s\n",
string(buffer[0:n-1]), connection.RemoteAddr().String())
_, err = connection.Write(buffer) // отправка сообщения клиенту
checkErr(err)
}
}
func RunServer(port int) {
// net.Listen возвращает Listener переменную,
// которая является общим сетевым прослушивателем для потоковых протоколов
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
checkErr(err)
defer listener.Close()
for {
connection, err := listener.Accept() // ожидание подключения клиента к серверу
// Только после успешного вызова Accept()TCP-сервер может начать
// взаимодействовать с TCP-клиентами
if err != nil {
log.Print(err)
return
}
go handleConnection(connection)
}
}

View File

@@ -0,0 +1,77 @@
package client
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"strings"
"sync"
)
var waitingGr sync.WaitGroup
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func sendMessage(connection net.Conn, waitingGr *sync.WaitGroup) {
defer waitingGr.Done()
for {
reader := bufio.NewReader(os.Stdin) // считываем введенное сообщение
text, _ := reader.ReadString('\n')
if strings.TrimSuffix(strings.TrimSpace(text), "\r\n") == "/stop" {
// завершение работы клиента
break
} else {
_, err := connection.Write([]byte(text))
checkErr(err)
}
}
}
func receiveMessage(connection net.Conn, waitingGr *sync.WaitGroup) {
defer waitingGr.Done()
for {
// ждем сообщение от сервера и считываем его
message, err := bufio.NewReader(connection).ReadString('\n')
if err == io.EOF {
fmt.Println("Connection close!")
break
} else if err != nil {
fmt.Println(err.Error())
break
}
message = message[:len(message)-1] // обрезаем символ перевода на следующую строку
fmt.Println(string(message))
}
}
func RunClient(port int) {
// запускает реализацию клиента TCP и соединяет вас с нужным TCP-сервером
connection, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
checkErr(err)
fmt.Printf("The TCP server is %s\n", connection.RemoteAddr().String())
defer connection.Close()
waitingGr.Add(1)
fmt.Println("Enter name: ")
temp := bufio.NewReader(os.Stdin)
userName, _ := temp.ReadString('\n')
_, err = connection.Write([]byte(userName))
checkErr(err)
go sendMessage(connection, &waitingGr)
go receiveMessage(connection, &waitingGr)
waitingGr.Wait()
}

View File

@@ -0,0 +1,3 @@
module tcp/server
go 1.24

View File

@@ -0,0 +1,38 @@
package main
import (
"fmt"
"log"
"os"
"strconv"
"tcp/server/client"
"tcp/server/server"
)
const PORT = 8081
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("You didn't select a launch option!!!")
return
}
numGR, err := strconv.Atoi(os.Args[1])
checkErr(err)
switch numGR {
case 1:
fmt.Println("Server is running")
server.RunServer(PORT)
case 2:
fmt.Println("Client is running")
client.RunClient(PORT)
default:
log.Fatal("What pokemon is this?")
}
}

View File

@@ -0,0 +1,85 @@
package server
import (
"bufio"
"fmt"
"log"
"net"
)
var connections []net.Conn
func checkErr(err error) {
if err != nil {
log.Print(err)
}
}
func handleConnection(connection net.Conn) {
connections = append(connections, connection)
userName, _ := bufio.NewReader(connection).ReadString('\n')
userName = userName[:len(userName)-2]
_, err := connection.Write([]byte("Hi " + userName + "\n"))
checkErr(err)
for {
text, err := bufio.NewReader(connection).ReadString('\n')
if err != nil {
connection.Close()
removeConnection(connection)
broadCastMessage(userName+" is offline\n", connection)
break
}
broadCastMessage(userName+":"+text, connection)
}
}
func removeConnection(connection net.Conn) {
var i int
for i = range connections {
if connections[i] == connection {
break
}
}
if len(connections) > 1 {
connections = append(connections[:i], connections[i+1:]...)
} else {
connections = nil
}
}
func broadCastMessage(msg string, connection net.Conn) {
// отправка сообщения всем клиентам
for _, c := range connections {
if connection != c {
_, err := c.Write([]byte(msg))
checkErr(err)
}
}
msg = msg[:len(msg)-1]
fmt.Println(msg)
}
func RunServer(port int) {
// net.Listen возвращает Listener переменную,
// которая является общим сетевым прослушивателем для потоковых протоколов
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
checkErr(err)
defer listener.Close()
for {
connection, err := listener.Accept() // ожидание подключения клиента к серверу
// Только после успешного вызова Accept()TCP-сервер может начать
// взаимодействовать с TCP-клиентами
if err != nil {
log.Print(err)
return
}
go handleConnection(connection)
}
}

View File

@@ -0,0 +1,5 @@
{
"yaml.schemas": {
"swaggerviewer:openapi": "file:///e%3A/code/golang/todo-service/todo-service-api.yml"
}
}

View File

@@ -0,0 +1,98 @@
package db
import (
_ "database/sql"
"fmt"
"log"
"os"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type SQLiteRepository struct {
db *gorm.DB
}
func NewSQLiteRepository() *SQLiteRepository {
var db *gorm.DB
if _, err := os.Stat(dbName); os.IsNotExist(err) {
db, err = gorm.Open(sqlite.Open(dbName), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
fmt.Println("DB isn't exist")
db.AutoMigrate(&Project{}, &Task{})
putDefaultValuesToDB(db)
} else {
db, err = gorm.Open(sqlite.Open(dbName), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
fmt.Println("DB already exists")
}
return &SQLiteRepository{
db: db,
}
}
func putDefaultValuesToDB(db *gorm.DB) {
firstProject := Project{
Name: "Go",
Description: "Roadmap for learning Go",
}
secondProject := Project{
Name: "One Year",
Description: "Tasks for the year",
}
db.Create(&firstProject)
db.Create(&secondProject)
db.Create(&Task{
Name: "Variable",
Description: "Learning Go build-in variables",
Priority: 1,
Project: &firstProject,
})
db.Create(&Task{
Name: "Struct",
Description: "Learning use struct in OOP code",
Priority: 3,
Project: &firstProject,
})
db.Create(&Task{
Name: "Goroutine",
Description: "Learning concurrent programming",
Priority: 5,
Project: &firstProject,
})
db.Create(&Task{
Name: "DataBase",
Description: "How write app with db",
Priority: 1,
Project: &firstProject,
})
db.Create(&Task{
Name: "PhD",
Description: "Ph.D. in Technical Sciences",
Priority: 5,
Project: &secondProject,
})
db.Create(&Task{
Name: "Losing weight",
Description: "Exercise and eat less chocolate",
Priority: 2,
Project: &secondProject,
})
db.Create(&Task{
Name: "Пафос и превозмогание",
Description: "10к подписчиков на канале",
Priority: 2,
Project: &secondProject,
})
}
func (r *SQLiteRepository) Close() {
}

View File

@@ -0,0 +1,12 @@
package db
import "errors"
const dbName = "todo.db"
var (
ErrDuplicate = errors.New("record already exists")
ErrNotExists = errors.New("row not exists")
ErrUpdateFailed = errors.New("update failed")
ErrDeleteFailed = errors.New("delete failed")
)

View File

@@ -0,0 +1,17 @@
package db
type Project struct {
ID int `json:"id" gorm:"primary_key;autoIncrement:true;not null"`
Name string `json:"name" gorm:"unique;not null"`
Description string `json:"description"`
}
type Task struct {
ID int `json:"id" gorm:"primary_key;autoIncrement;not null"`
Name string `json:"name" gorm:"not null"`
Description string `json:"description" gorm:"not null"`
Priority uint8 `json:"priority" gorm:"not null"`
IsDone bool `json:"isDone" gorm:"not null"`
ProjectID int `json:"projectID" gorm:"not null"`
Project *Project `gorm:"foreignKey:ProjectID;references:ID"`
}

View File

@@ -0,0 +1,49 @@
package db
import (
"errors"
"github.com/mattn/go-sqlite3"
)
func (r *SQLiteRepository) AddProject(project Project) (*Project, error) {
tx := r.db.Create(&project)
if tx.Error != nil {
var sqliteErr sqlite3.Error
if errors.As(tx.Error, &sqliteErr) {
if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) {
return nil, ErrDuplicate
}
}
return nil, tx.Error
}
return &project, nil
}
func (r *SQLiteRepository) DeleteProject(projectID int) error {
tx := r.db.Delete(&Project{ID: projectID})
if tx.Error != nil {
return tx.Error
}
rowsAffected := tx.RowsAffected
if rowsAffected == 0 {
return ErrDeleteFailed
}
return nil
}
func (r *SQLiteRepository) GetAllProjects() ([]Project, error) {
var projects []Project
tx := r.db.Find(&projects)
if tx.Error != nil {
return nil, tx.Error
}
if tx.RowsAffected == 0 {
return nil, ErrNotExists
}
return projects, nil
}

View File

@@ -0,0 +1,73 @@
package db
import "errors"
func (r *SQLiteRepository) AddTask(task Task) (*Task, error) {
tx := r.db.Create(&task)
if tx.Error != nil {
return nil, tx.Error
}
return &task, nil
}
func (r *SQLiteRepository) DeleteTask(taskID int) error {
tx := r.db.Delete(&Task{ID: taskID})
if tx.Error != nil {
return tx.Error
}
rowsAffected := tx.RowsAffected
if rowsAffected == 0 {
return ErrDeleteFailed
}
return nil
}
func (r *SQLiteRepository) GetAllTasks() (tasks []Task, err error) {
tx := r.db.Find(&tasks)
if tx.Error != nil {
return nil, tx.Error
}
if tx.RowsAffected == 0 {
return nil, ErrNotExists
}
return
}
func (r *SQLiteRepository) GetProjectTasks(projectID int) (tasks []Task, err error) {
if projectID == 0 {
return nil, errors.New("invalid updated ID")
}
tx := r.db.Where("project_id", projectID).Find(&tasks)
if tx.Error != nil {
return nil, tx.Error
}
if tx.RowsAffected == 0 {
return nil, ErrNotExists
}
return
}
func (r *SQLiteRepository) TaskDone(taskId int) error {
if taskId == 0 {
return errors.New("invalid updated ID")
}
pjTask := &Task{ID: taskId}
tx := r.db.Find(&pjTask)
if tx.Error != nil {
return tx.Error
}
pjTask.IsDone = true
r.db.Save(&pjTask)
rowsAffected := tx.RowsAffected
if rowsAffected == 0 {
return ErrUpdateFailed
}
return nil
}

View File

@@ -0,0 +1,16 @@
module golang/todo-service
go 1.24
require (
github.com/gorilla/mux v1.8.1
github.com/mattn/go-sqlite3 v1.14.24
gorm.io/driver/sqlite v1.5.7
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
golang.org/x/text v0.14.0 // indirect
gorm.io/gorm v1.25.12 // indirect
)

View File

@@ -0,0 +1,16 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

View File

@@ -0,0 +1,20 @@
package main
import (
"golang/todo-service/db"
"golang/todo-service/service"
"log"
"net/http"
)
const PORT = "8080"
func main() {
rep := db.NewSQLiteRepository()
defer rep.Close()
router := service.NewRouter(rep)
log.Printf("Server started")
log.Fatal(http.ListenAndServe(":"+PORT, router))
}

View File

@@ -0,0 +1,22 @@
package service
import "golang/todo-service/db"
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
type GoodResponse struct {
Code int `json:"code"`
Message string `json:"message"`
ID int `json:"id"`
}
type ProjectsList struct {
Items []db.Project `json:"items,omitempty"`
}
type TasksList struct {
Items []db.Task `json:"items,omitempty"`
}

View File

@@ -0,0 +1,87 @@
package service
import (
"encoding/json"
"golang/todo-service/db"
"io"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func deleteProject(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
params := mux.Vars(r)
idStr, ok := params["id"]
if !ok {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
id, err := strconv.Atoi(idStr)
if err != nil {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
err = repository.DeleteProject(id)
if err != nil {
errorResponse(w, ErrorResponse{http.StatusNotFound,
"Project not found"})
return
}
w.WriteHeader(http.StatusOK)
}
func addProject(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var project db.Project
reqBody, err := io.ReadAll(r.Body)
if err == nil {
json.Unmarshal(reqBody, &project)
if project.Name == "" {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
project.ID = 0
project, err := repository.AddProject(project)
if err != nil {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Project with that name already exists"})
return
}
goodResponse := GoodResponse{
Code: 201,
Message: "Проект создан",
ID: project.ID,
}
jsonGoodResponse, _ := json.Marshal(goodResponse)
w.WriteHeader(http.StatusCreated)
w.Write(jsonGoodResponse)
return
}
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
}
func getProjects(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
progects, err := repository.GetAllProjects()
if err != nil || len(progects) == 0 {
errorResponse(w, ErrorResponse{http.StatusNotFound,
"Projects not found"})
return
}
projectsList := ProjectsList{progects}
jsonGoodResponse, _ := json.Marshal(projectsList)
w.WriteHeader(http.StatusOK)
w.Write(jsonGoodResponse)
}

View File

@@ -0,0 +1,153 @@
package service
import (
"encoding/json"
"golang/todo-service/db"
"io"
"net/http"
"strconv"
)
func deleteTask(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
queryStr := r.URL.Query()
idStr, ok := queryStr["id"]
if !ok {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
if len(idStr) == 0 {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
id, err := strconv.Atoi(idStr[0])
if err != nil {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
err = repository.DeleteTask(id)
if err != nil {
errorResponse(w, ErrorResponse{http.StatusNotFound,
"Task not found"})
return
}
w.WriteHeader(http.StatusOK)
}
func getTask(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
queryStr := r.URL.Query()
idStr, ok := queryStr["projectID"]
if !ok {
tasks, err := repository.GetAllTasks()
if err != nil || len(tasks) == 0 {
errorResponse(w, ErrorResponse{http.StatusNotFound,
"Tasks not found"})
return
}
// возвращаем все имеющиеся задачи
projectsList := TasksList{tasks}
jsonGoodResponse, _ := json.Marshal(projectsList)
w.WriteHeader(http.StatusOK)
w.Write(jsonGoodResponse)
return
}
if len(idStr) == 0 {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
id, err := strconv.Atoi(idStr[0])
if err != nil {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
tasks, err := repository.GetProjectTasks(id)
if err != nil || len(tasks) == 0 {
errorResponse(w, ErrorResponse{http.StatusNotFound,
"Tasks not found"})
return
}
// возвращаем все задачи конкретного проекта
projectsList := TasksList{tasks}
jsonGoodResponse, _ := json.Marshal(projectsList)
w.WriteHeader(http.StatusOK)
w.Write(jsonGoodResponse)
}
func addTask(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var task db.Task
reqBody, err := io.ReadAll(r.Body)
if err == nil {
json.Unmarshal(reqBody, &task)
if task.Name == "" || task.ProjectID == 0 {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
task.ID = 0
task, err := repository.AddTask(task)
if err != nil {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
goodResponse := GoodResponse{
Code: 201,
Message: "Задача создана",
ID: task.ID,
}
jsonGoodResponse, _ := json.Marshal(goodResponse)
w.WriteHeader(http.StatusCreated)
w.Write(jsonGoodResponse)
return
}
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
}
func doneTask(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
queryStr := r.URL.Query()
idStr, ok := queryStr["id"]
if !ok {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
if len(idStr) == 0 {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
id, err := strconv.Atoi(idStr[0])
if err != nil {
errorResponse(w, ErrorResponse{http.StatusBadRequest,
"Validation Failed"})
return
}
err = repository.TaskDone(id)
if err != nil {
errorResponse(w, ErrorResponse{http.StatusNotFound,
"Tasks not found"})
return
}
w.WriteHeader(http.StatusOK)
}

View File

@@ -0,0 +1,23 @@
package service
import (
"log"
"net/http"
"time"
)
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}

View File

@@ -0,0 +1,102 @@
package service
import (
"encoding/json"
"fmt"
"golang/todo-service/db"
"net/http"
"github.com/gorilla/mux"
)
type Route struct {
Name string // имя функции обработчика
Method string // тип HTTP-сообщения
Pattern string // шаблон пути
HandlerFunc http.HandlerFunc // ссылка на функцию обработчик
// сигнатура функции должна быть func(ResponseWriter, *Request)
}
var repository *db.SQLiteRepository
func NewRouter(rep *db.SQLiteRepository) *mux.Router {
repository = rep
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
var handler http.Handler
handler = route.HandlerFunc
//оборачиваем функцию-обработчик в логгер
handler = Logger(route.HandlerFunc, route.Name)
// handler := route.HandlerFunc
// // // добавляем новый обработчик
router. //HandleFunc(route.Pattern, handler).Methods(route.Method)
Methods(route.Method). // тип HTTP-сообщения
Path(route.Pattern). // шаблон пути
Name(route.Name). // имя функции обработчика
Handler(handler) // ссылка на функцию обработчик
}
router.Use(mux.CORSMethodMiddleware(router))
return router
}
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Wold in REST API style!!!")
}
var routes = []Route{
{ // домашняя страница
"home",
http.MethodGet,
"/api/v1/todo",
home,
},
{ // удаление проекта по id
"deleteProject",
http.MethodDelete,
"/api/v1/todo/project/del/{id}",
deleteProject,
},
{ // добавление проекта
"addProject",
http.MethodPost,
"/api/v1/todo/project",
addProject,
},
{ // получить все проекты
"getProjects",
http.MethodGet,
"/api/v1/todo/projects",
getProjects,
},
{ // удаление задачи
"deleteTask",
http.MethodDelete,
"/api/v1/todo/task",
deleteTask,
},
{ // получение всех задач или конкретного проекта
"getTask",
http.MethodGet,
"/api/v1/todo/task",
getTask,
},
{ // добавить задачу
"addTask",
http.MethodPost,
"/api/v1/todo/task",
addTask,
},
{ // изменение статуса задачи на «Выполнено»
"doneTask",
http.MethodPut,
"/api/v1/todo/task",
doneTask,
},
}
func errorResponse(w http.ResponseWriter, err ErrorResponse) {
jsonResponse, _ := json.Marshal(err)
w.WriteHeader(err.Code)
w.Write(jsonResponse)
}

View File

@@ -0,0 +1,429 @@
openapi: 3.0.2
info:
title: ToDo client-server api
description: |
Документация по описанию конечных точек сервера, посредством которых
происходит доступ к ресурсам
version: 1.0.0
servers:
- url: http://127.0.0.1:8080/api/v1/todo
tags:
- name: task
- name: project
paths:
/project:
post:
tags:
- project
description: |
Добавление проекта
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Project'
responses:
"201":
description: Добавление прошло успешно.
content:
application/json:
schema:
$ref: '#/components/schemas/GoodResponse'
examples:
response:
value: |-
{
"code": 201,
"message": "Project created",
"id": 1
}
"400":
description: Невалидная схема проекта или входные данные не верны.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
response:
value: |-
{
"code": 400,
"message": "Validation Failed"
}
/project/del/{id}:
delete:
tags:
- project
description: |
Удалить проект
parameters:
- description: Идентификатор
in: path
name: id
required: true
schema:
type: integer
example: 1
responses:
"200":
description: Удаление прошло успешно.
"400":
description: Невалидная схема проекта или входные данные не верны.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 400,
"message": "Validation Failed"
}
"404":
description: Проект не найден.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 404,
"message": "Project not found"
}
/projects:
get:
tags:
- project
description: |
Получить список проектов
responses:
"200":
description: Запрос прошел успешно
content:
application/json:
schema:
$ref: "#/components/schemas/ProgectsList"
"404":
description: Проект не найден.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 404,
"message": "Projects not found"
}
/task:
put:
tags:
- task
description: |
Изменение статуса задачи
parameters:
- in: query
required: true
name: id
schema:
type: integer
example: 1
responses:
"201":
description: Запрос прошел успешно
"400":
description: Невалидная схема задачи или входные данные не верны.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 400,
"message": "Validation Failed"
}
"404":
description: Проект не найден.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 404,
"message": "Tasks not found"
}
post:
tags:
- task
description: |
Добавление задачи
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
responses:
"201":
description: Добавление прошло успешно.
content:
application/json:
schema:
$ref: "#/components/schemas/GoodResponse"
examples:
response:
value: |-
{
"code": 201,
"message": "Task created",
"id": 1
}
"400":
description: Невалидная схема задачи или входные данные не верны.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 400,
"message": "Validation Failed"
}
get:
tags:
- task
description: |
Получение списка всех задач или конкретного проекта
parameters:
- in: query
name: projectID
schema:
type: integer
example: 1
responses:
"200":
description: Запрос прошел успешно
content:
application/json:
schema:
$ref: "#/components/schemas/TasksList"
"400":
description: Невалидная схема задачи или входные данные не верны.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 400,
"message": "Validation Failed"
}
"404":
description: Проект не найден.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 404,
"message": "Tasks not found"
}
delete:
tags:
- task
description: |
Удаление задачи с заданным ID
parameters:
- in: query
required: true
name: id
schema:
type: integer
example: 1
responses:
"200":
description: Запрос прошел успешно
"400":
description: Невалидная схема задачи или входные данные не верны.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 400,
"message": "Validation Failed"
}
"404":
description: Задача не найдена.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
response:
value: |-
{
"code": 404,
"message": "Task not found"
}
components:
schemas:
Error:
required:
- code
- message
properties:
code:
type: integer
nullable: false
message:
type: string
nullable: false
GoodResponse:
required:
- code
- id
- message
properties:
code:
type: integer
nullable: false
message:
type: string
nullable: false
id:
type: integer
nullable: false
example:
code: 0
id: 6
message: message
Project:
required:
- description
- id
- name
type: object
properties:
id:
type: integer
description: id project
name:
type: string
description: Имя проекта
nullable: false
description:
type: string
description: Описание проекта
nullable: false
example:
id: 1
name: Пафос и Превозмогание
description: Прожать батоны и вперед!!!
ProgectsList:
type: object
properties:
items:
type: array
description: Список существующих проектов
nullable: false
items:
$ref: '#/components/schemas/Project'
example:
items:
- id: 1
name: Пафос и Превозмогание
description: Прожать батоны и вперед!!!
- id: 1
name: Пафос и Превозмогание
description: Прожать батоны и вперед!!!
Task:
required:
- description
- id
- isDone
- name
- projectID
- priority
type: object
properties:
id:
type: integer
description: id task
name:
type: string
description: Имя задачи
nullable: false
description:
type: string
description: Описание задачи
nullable: false
priority:
type: integer
description: приоритет задачи
nullable: false
isDone:
type: boolean
description: Флаг о выполнении задачи
nullable: false
projectID:
type: integer
description: id task
nullable: false
example:
id: 1
name: 10к подписчиков
description: Прожать батоны и вперед!!!
isDone: false
projectID: 1
priority: 3
TasksList:
type: object
properties:
items:
type: array
description: Список существующих проектов
nullable: false
items:
$ref: '#/components/schemas/Task'
example:
items:
- id: 1
name: 10к подписчиков
description: Прожать батоны и вперед!!!
isDone: false
projectID: 1
- id: 1
name: 10к подписчиков
description: Прожать батоны и вперед!!!
isDone: false
projectID: 1

Binary file not shown.

View File

@@ -35,18 +35,6 @@ func (b *Board) IsEmpty() bool {
return true
}
// Отображение игрового поля
// ToStringSlice converts the board to a slice of strings for serialization.
func (b *Board) ToStringSlice() []string {
slice := make([]string, 0, b.Size*b.Size)
for i := 0; i < b.Size; i++ {
for j := 0; j < b.Size; j++ {
slice = append(slice, b.Board[i][j].String())
}
}
return slice
}
// Отображение игрового поля
func (b *Board) PrintBoard() {
fmt.Print(" ")

View File

@@ -8,16 +8,3 @@ const (
Cross
Nought
)
func (bf BoardField) String() string {
switch bf {
case Empty:
return "."
case Cross:
return "X"
case Nought:
return "O"
default:
return "?"
}
}

View File

@@ -38,9 +38,13 @@ func (r *SQLiteRepository) GetAllFinishedGames() (*[]m.FinishGameSnapshot, error
return &finishGameSnapshots, nil
}
func (r *SQLiteRepository) GetFinishedGameById(id int) (*m.FinishGameSnapshot, error) {
func (r *SQLiteRepository) GetFinishedGameById(
id int,
) (*m.FinishGameSnapshot, error) {
var playerFinishGame PlayerFinishGame
if err := r.db.Where("id = ?", id).First(&playerFinishGame).Error; err != nil {
if err := r.db.Where("id = ?", id).First(
&playerFinishGame,
).Error; err != nil {
return nil, err
}
return playerFinishGame.ToModel()

View File

@@ -1,14 +1,48 @@
package database
import "time"
import (
"encoding/json"
b "tic-tac-toe/board"
m "tic-tac-toe/model"
"time"
)
// PlayerFinishGame представляет модель таблицы
// для хранения завершенной игры в БД
type PlayerFinishGame struct {
ID int `gorm:"primary_key;autoIncrement;not null"`
WinnerName string `gorm:"not null"`
AnotherPlayerName string `gorm:"not null"`
BoardJSON []byte `gorm:"type:json;not null"`
PlayerFigure int `gorm:"not null"`
Time time.Time `gorm:"not null"`
// ID игры
ID int `gorm:"primary_key;autoIncrement;not null"`
// Имя победителя
WinnerName string `gorm:"not null"`
// Имя противника
AnotherPlayerName string `gorm:"not null"`
// JSON-представление доски
BoardJSON []byte `gorm:"type:json;not null"`
// Символ победившего игрока
PlayerFigure int `gorm:"not null"`
// Время завершения игры
Time time.Time `gorm:"not null"`
}
// Задаем имя таблицы для структуры PlayerFinishGame
func (pfg *PlayerFinishGame) TableName() string {
return "player_finish_games"
}
// Преобразуем таблицу PlayerFinishGame в модель PlayerFinishGame
// из пакета model
func (f *PlayerFinishGame) ToModel() (*m.FinishGameSnapshot, error) {
var board b.Board
if err := json.Unmarshal(f.BoardJSON, &board); err != nil {
return nil, err
}
return &m.FinishGameSnapshot{
ID: f.ID,
Board: &board,
PlayerFigure: b.BoardField(f.PlayerFigure),
WinnerName: f.WinnerName,
AnotherPlayerName: f.AnotherPlayerName,
Time: f.Time,
}, nil
}

View File

@@ -1,31 +0,0 @@
package database
import (
"encoding/json"
b "tic-tac-toe/board"
m "tic-tac-toe/model"
)
// Задаем имя таблицы для структуры PlayerFinishGame
func (pfg *PlayerFinishGame) TableName() string {
return "player_finish_games"
}
// Преобразуем таблицу PlayerFinishGame в модель PlayerFinishGame
// из пакета model
func (f *PlayerFinishGame) ToModel() (*m.FinishGameSnapshot, error) {
var board b.Board
if err := json.Unmarshal(f.BoardJSON, &board); err != nil {
return nil, err
}
return &m.FinishGameSnapshot{
ID: f.ID,
Board: &board,
PlayerFigure: b.BoardField(f.PlayerFigure),
WinnerName: f.WinnerName,
AnotherPlayerName: f.AnotherPlayerName,
Time: f.Time,
}, nil
}

View File

@@ -6,10 +6,16 @@ import (
)
type FinishGameSnapshot struct {
ID int `json:"id"`
Board *board.Board `json:"board"`
PlayerFigure board.BoardField `json:"player_figure"`
WinnerName string `json:"winner_name"`
AnotherPlayerName string `json:"another_player_name"`
Time time.Time `json:"time"`
// Идентификатор игры
ID int `json:"id"`
// Состояние доски в момент завершения игры
Board *board.Board `json:"board"`
// Символ (Х/О) победившего игрока
PlayerFigure board.BoardField `json:"player_figure"`
// Имя победителя
WinnerName string `json:"winner_name"`
// Имя противника
AnotherPlayerName string `json:"another_player_name"`
// Время завершения игры
Time time.Time `json:"time"`
}

View File

@@ -1,7 +1,8 @@
package network
// Сообщения от клиента к серверу
const (
// Client to Server Commands
CmdNickname Command = "nickname"
CmdJoinRoomRequest Command = "join_room"
CmdLeaveRoomRequest Command = "leave_room"
@@ -11,29 +12,29 @@ const (
CmdFinishedGameByIdRequest Command = "get_finished_game_by_id"
)
// LoginRequest отправляется клиентом для входа в систему.
// Запрос от клиента для входа в систему
type NicknameRequest struct {
Nickname string `json:"nickname"`
}
// JoinRoomRequest отправляется клиентом для подключения к существующей комнате.
// Запрос от клиента для подключения к существующей комнате
type JoinRoomRequest struct {
RoomName string `json:"room_name"`
PlayerName string `json:"player_name"`
}
// LeaveRoomRequest отправляется клиентом для выхода из текущей комнаты.
// Запрос от клиента для выхода из текущей комнаты
type LeaveRoomRequest struct {
RoomName string `json:"room_name"`
PlayerName string `json:"player_name"`
}
// ListRoomsRequest отправляется клиентом для получения списка доступных комнат.
// Обычно для этого запроса не требуется специальная полезная нагрузка.
// Запрос от клиента для получения списка доступных комнат
// Обычно для этого запроса не требуется специальная полезная нагрузка
type ListRoomsRequest struct {
}
// MakeMoveRequest отправляется клиентом для совершения хода в игре.
// Сообщение с данными о ходе игрока
type MakeMoveRequest struct {
RoomName string `json:"room_name"`
PlayerName string `json:"player_name"`
@@ -41,12 +42,11 @@ type MakeMoveRequest struct {
PositionCol int `json:"position_col"`
}
// GetFinishedGamesRequest отправляется клиентом для получения списка завершенных игр.
// Обычно для этого запроса не требуется специальная полезная нагрузка, если запрашиваются все игры для пользователя.
// Запрос от клиента для получения списка завершенных игр
type GetFinishedGamesRequest struct {
}
// GetFinishedGameByIdRequest отправляется клиентом для получения конкретной завершенной игры.
// Запрос от клиента для получения конкретной завершенной игры
type GetFinishedGameByIdRequest struct {
GameID int `json:"game_id"`
}

View File

@@ -2,8 +2,13 @@ package network
import "encoding/json"
// Объявляем тип команды
type Command string
// Базовая структура сообщения
// Cmd - тип команды
// Payload - полезная нагрузка, которая должна быть
// сериализована в JSON ([]byte). Может быть nil
type Message struct {
Cmd Command `json:"command"`
Payload json.RawMessage `json:"payload,omitempty"`

View File

@@ -6,8 +6,8 @@ import (
"tic-tac-toe/model"
)
// Сообщения от сервера к клиенту
const (
// Server to Client Commands
CmdUpdateState Command = "update_state"
CmdError Command = "error"
CmdNickNameResponse Command = "nick_name_response"
@@ -21,26 +21,25 @@ const (
CmdFinishedGameResponse Command = "finished_game_response"
)
// сообщение о том, что противник покинул игру
// инициализирующее сообщение в начале партии
// InitGameResponse отправляется сервером при инициализации игры.
// Отправляется сервером при инициализации игры
type InitGameResponse struct {
Board b.Board `json:"board"`
CurrentPlayer b.BoardField `json:"current_player"`
}
// EndGameResponse отправляется сервером при завершении игры.
// Отправляется сервером при завершении игры
type EndGameResponse struct {
Board b.Board `json:"board"`
CurrentPlayer b.BoardField `json:"current_player"`
}
// Сообщение о том, что противник покинул игру
// инициализирующее сообщение в начале партии
type OpponentLeft struct {
Nickname string `json:"nickname"`
}
// RoomInfo содержит информацию о комнате.
// Содержит информацию о состоянии комнаты
type RoomInfo struct {
Name string `json:"name"`
BoardSize int `json:"board_size"`
@@ -49,46 +48,46 @@ type RoomInfo struct {
Difficult g.Difficulty `json:"difficult"`
}
// RoomListсодержит список доступных комнат.
// Отправляем клиенту при запросе списка доступных комнат
type RoomListResponse struct {
Rooms []RoomInfo `json:"rooms"`
}
// GameStateUpdate содержит информацию об обновлении состояния игры.
// Отправляем клиенту при обновлении состояния игры
type GameStateUpdate struct {
Board b.Board `json:"board"`
CurrentPlayer b.BoardField `json:"current_player"`
}
// ErrorResponse отправляется сервером при возникновении ошибки.
// Отправляется сервером при возникновении ошибки
type ErrorResponse struct {
Message string `json:"message"`
}
// NickNameResponse отправляется сервером при успешном входе клиента.
// Отправляется сервером при успешном входе клиента
type NickNameResponse struct {
Nickname string `json:"nickname"`
}
// RoomCreatedResponse отправляется сервером после успешного создания комнаты.
// Отправляется сервером после успешного создания комнаты
type RoomCreatedResponse struct {
RoomID string `json:"room_id"`
RoomName string `json:"room_name"`
}
// RoomJoinResponse отправляется сервером, когда клиент успешно присоединился к комнате.
// Отправляется сервером, когда клиент успешно присоединился к комнате
type RoomJoinResponse struct {
RoomName string `json:"room_name"`
PlayerSymbol b.BoardField `json:"player_symbol"`
Board b.Board `json:"board"`
}
// FinishedGamesResponse отправляется сервером со списком завершенных игр.
// Отправляется сервером за запрос о списке завершенных игр
type FinishedGamesResponse struct {
Games *[]model.FinishGameSnapshot `json:"games"`
}
// FinishedGameResponse отправляется сервером с информацией о конкретной завершенной игре.
// Отправляется сервером с информацией о конкретной завершенной игре
type FinishedGameResponse struct {
Game *model.FinishGameSnapshot `json:"game"`
}

View File

@@ -19,8 +19,7 @@ type ComputerPlayer struct {
// Создаем нового игрока-компьютера с заданным уровнем сложности
func NewComputerPlayer(
figure b.BoardField,
difficulty g.Difficulty,
figure b.BoardField, difficulty g.Difficulty,
) *ComputerPlayer {
source := rand.NewSource(time.Now().UnixNano())
return &ComputerPlayer{
@@ -45,12 +44,8 @@ func (p *ComputerPlayer) GetNickname() string {
return "Computer"
}
func (p *ComputerPlayer) SwitchPlayer() {
if p.Figure == b.Cross {
p.Figure = b.Nought
} else {
p.Figure = b.Cross
}
func (p *ComputerPlayer) CheckSocket(conn net.Conn) bool {
return false
}
func (p *ComputerPlayer) GetFigure() b.BoardField {
@@ -61,6 +56,14 @@ func (p *ComputerPlayer) IsComputer() bool {
return true
}
func (p *ComputerPlayer) SwitchPlayer() {
if p.Figure == b.Cross {
p.Figure = b.Nought
} else {
p.Figure = b.Cross
}
}
// Реализуем ход компьютера в зависимости от выбранной сложности
func (p *ComputerPlayer) MakeMove(board *b.Board) (int, int, bool) {
fmt.Printf("%s (Computer) making move... ", p.GetSymbol())
@@ -91,10 +94,6 @@ func (p *ComputerPlayer) makeEasyMove(board *b.Board) (int, int) {
return emptyCells[randomIndex][0], emptyCells[randomIndex][1]
}
func (p *ComputerPlayer) CheckSocket(conn net.Conn) bool {
return false
}
// Средний уровень: проверяет возможность выигрыша
// или блокировки выигрыша противника
func (p *ComputerPlayer) makeMediumMove(board *b.Board) (int, int) {

View File

@@ -11,22 +11,25 @@ type IPlayer interface {
// Получение символа игрока (X или O)
GetSymbol() string
// Переключение хода на другого игрока
// Переключение хода на другой символ
SwitchPlayer()
// Отправка сообщения игроку-клиенту
SendMessage(msg *network.Message)
// Получение никнейма игрока
GetNickname() string
// Получение текущей фигуры игрока
GetFigure() b.BoardField
// Выполнение хода игрока
// Метод для выполнения хода компьютером
// Возвращает координаты хода (x, y) и признак успешности
MakeMove(board *b.Board) (int, int, bool)
// Проверка, является ли игрок компьютером
IsComputer() bool
// Проверка владения сокетом
CheckSocket(conn net.Conn) bool
}

View File

@@ -2,10 +2,7 @@ package player
import (
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
b "tic-tac-toe/board"
"tic-tac-toe/network"
)
@@ -20,7 +17,9 @@ type HumanPlayer struct {
func NewHumanPlayer(
nickname string, conn *net.Conn,
) *HumanPlayer {
return &HumanPlayer{Figure: b.Cross, Nickname: nickname, Conn: conn}
return &HumanPlayer{
Figure: b.Cross, Nickname: nickname, Conn: conn,
}
}
func (p *HumanPlayer) CheckSocket(conn net.Conn) bool {
@@ -58,38 +57,11 @@ func (p *HumanPlayer) GetFigure() b.BoardField {
}
// Метод-заглушка, т.к. ввод игрока осуществляется на
// уровне пакета game, где нужно еще отрабатывать
// команду на выход и сохранение игровой сессии
// клиентской стороне
func (p *HumanPlayer) MakeMove(board *b.Board) (int, int, bool) {
return -1, -1, false
}
// Обрабатываем строку ввода и
// преобразуем ее в координаты хода
func (p *HumanPlayer) ParseMove(
input string,
board *b.Board,
) (int, int, bool) {
parts := strings.Fields(input)
if len(parts) != 2 {
fmt.Println("Invalid input. Please try again.")
return -1, -1, false
}
row, err1 := strconv.Atoi(parts[0])
col, err2 := strconv.Atoi(parts[1])
if err1 != nil || err2 != nil ||
row < 1 || col < 1 || row > board.Size ||
col > board.Size {
fmt.Println("Invalid input. Please try again.")
return -1, -1, false
}
// Преобразуем введенные координаты (начиная с 1)
// в индексы массива (начиная с 0)
return row - 1, col - 1, true
}
func (p *HumanPlayer) IsComputer() bool {
return false
}

View File

@@ -76,7 +76,10 @@ func (s *Server) handleConnection(conn net.Conn) {
for {
var msg network.Message
if err := decoder.Decode(&msg); err != nil {
log.Printf("Client %s disconnected: %v", conn.RemoteAddr(), err)
log.Printf(
"Client %s disconnected: %v", conn.RemoteAddr(),
err,
)
s.disconnectedClientHandler(conn)
return
}