mirror of
https://github.com/MADTeacher/go_basics.git
synced 2025-11-23 21:34:47 +02:00
Глава 8
Допричесать игру
This commit is contained in:
46
part_8/8.1/udp/client/client.go
Normal file
46
part_8/8.1/udp/client/client.go
Normal 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
3
part_8/8.1/udp/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module udp
|
||||
|
||||
go 1.24
|
||||
38
part_8/8.1/udp/main.go
Normal file
38
part_8/8.1/udp/main.go
Normal 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?")
|
||||
}
|
||||
}
|
||||
44
part_8/8.1/udp/server/server.go
Normal file
44
part_8/8.1/udp/server/server.go
Normal 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)
|
||||
}
|
||||
}
|
||||
53
part_8/8.2/tcp/client/client.go
Normal file
53
part_8/8.2/tcp/client/client.go
Normal 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
3
part_8/8.2/tcp/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module udp/server
|
||||
|
||||
go 1.24
|
||||
38
part_8/8.2/tcp/main.go
Normal file
38
part_8/8.2/tcp/main.go
Normal 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?")
|
||||
}
|
||||
}
|
||||
62
part_8/8.2/tcp/server/server.go
Normal file
62
part_8/8.2/tcp/server/server.go
Normal 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)
|
||||
}
|
||||
}
|
||||
77
part_8/8.3/tcp-chat/client/client.go
Normal file
77
part_8/8.3/tcp-chat/client/client.go
Normal 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()
|
||||
}
|
||||
3
part_8/8.3/tcp-chat/go.mod
Normal file
3
part_8/8.3/tcp-chat/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module tcp/server
|
||||
|
||||
go 1.24
|
||||
38
part_8/8.3/tcp-chat/main.go
Normal file
38
part_8/8.3/tcp-chat/main.go
Normal 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?")
|
||||
}
|
||||
}
|
||||
85
part_8/8.3/tcp-chat/server/server.go
Normal file
85
part_8/8.3/tcp-chat/server/server.go
Normal 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)
|
||||
}
|
||||
}
|
||||
5
part_8/8.4/golang/todo-service/.vscode/settings.json
vendored
Normal file
5
part_8/8.4/golang/todo-service/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"swaggerviewer:openapi": "file:///e%3A/code/golang/todo-service/todo-service-api.yml"
|
||||
}
|
||||
}
|
||||
98
part_8/8.4/golang/todo-service/db/db.go
Normal file
98
part_8/8.4/golang/todo-service/db/db.go
Normal 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() {
|
||||
|
||||
}
|
||||
12
part_8/8.4/golang/todo-service/db/db_definition.go
Normal file
12
part_8/8.4/golang/todo-service/db/db_definition.go
Normal 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")
|
||||
)
|
||||
17
part_8/8.4/golang/todo-service/db/models.go
Normal file
17
part_8/8.4/golang/todo-service/db/models.go
Normal 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"`
|
||||
}
|
||||
49
part_8/8.4/golang/todo-service/db/projects_crud.go
Normal file
49
part_8/8.4/golang/todo-service/db/projects_crud.go
Normal 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
|
||||
}
|
||||
73
part_8/8.4/golang/todo-service/db/tasks_crud.go
Normal file
73
part_8/8.4/golang/todo-service/db/tasks_crud.go
Normal 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
|
||||
}
|
||||
16
part_8/8.4/golang/todo-service/go.mod
Normal file
16
part_8/8.4/golang/todo-service/go.mod
Normal 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
|
||||
)
|
||||
16
part_8/8.4/golang/todo-service/go.sum
Normal file
16
part_8/8.4/golang/todo-service/go.sum
Normal 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=
|
||||
20
part_8/8.4/golang/todo-service/main.go
Normal file
20
part_8/8.4/golang/todo-service/main.go
Normal 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))
|
||||
}
|
||||
22
part_8/8.4/golang/todo-service/service/api_models.go
Normal file
22
part_8/8.4/golang/todo-service/service/api_models.go
Normal 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"`
|
||||
}
|
||||
87
part_8/8.4/golang/todo-service/service/api_project.go
Normal file
87
part_8/8.4/golang/todo-service/service/api_project.go
Normal 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)
|
||||
}
|
||||
153
part_8/8.4/golang/todo-service/service/api_task.go
Normal file
153
part_8/8.4/golang/todo-service/service/api_task.go
Normal 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)
|
||||
}
|
||||
23
part_8/8.4/golang/todo-service/service/logger.go
Normal file
23
part_8/8.4/golang/todo-service/service/logger.go
Normal 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),
|
||||
)
|
||||
})
|
||||
}
|
||||
102
part_8/8.4/golang/todo-service/service/routers.go
Normal file
102
part_8/8.4/golang/todo-service/service/routers.go
Normal 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)
|
||||
}
|
||||
429
part_8/8.4/golang/todo-service/todo-service-api.yml
Normal file
429
part_8/8.4/golang/todo-service/todo-service-api.yml
Normal 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
|
||||
BIN
part_8/8.4/golang/todo-service/todo.db
Normal file
BIN
part_8/8.4/golang/todo-service/todo.db
Normal file
Binary file not shown.
@@ -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(" ")
|
||||
|
||||
@@ -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 "?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 игры
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user