mirror of
https://github.com/MADTeacher/go_basics.git
synced 2025-11-23 21:34:47 +02:00
Переработана глава про конкурентность
This commit is contained in:
77
part_7/7.10/fan-in.go
Normal file
77
part_7/7.10/fan-in.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TaskItem struct {
|
||||
id int
|
||||
}
|
||||
|
||||
func generateInputs(work *[]TaskItem) (in1, in2 <-chan TaskItem) {
|
||||
ch1 := make(chan TaskItem)
|
||||
ch2 := make(chan TaskItem)
|
||||
|
||||
go func() {
|
||||
defer close(ch1)
|
||||
defer close(ch2)
|
||||
|
||||
for it, value := range *work {
|
||||
if it%2 == 0 {
|
||||
ch1 <- value
|
||||
} else {
|
||||
ch2 <- value
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch1, ch2
|
||||
}
|
||||
|
||||
func main() {
|
||||
tasks := []TaskItem{
|
||||
{0}, {1}, {2},
|
||||
{3}, {4}, {5},
|
||||
{6}, {7}, {8},
|
||||
{9}, {10},
|
||||
}
|
||||
|
||||
in1, in2 := generateInputs(&tasks)
|
||||
|
||||
out := fanIn(in1, in2)
|
||||
|
||||
for value := range out {
|
||||
fmt.Println("Value:", value)
|
||||
}
|
||||
fmt.Println("Finished")
|
||||
}
|
||||
|
||||
func fanIn(inputs ...<-chan TaskItem) <-chan TaskItem {
|
||||
var wg sync.WaitGroup
|
||||
out := make(chan TaskItem)
|
||||
|
||||
wg.Add(len(inputs))
|
||||
|
||||
for _, in := range inputs {
|
||||
go func(ch <-chan TaskItem) {
|
||||
for {
|
||||
value, ok := <-ch
|
||||
|
||||
if !ok {
|
||||
wg.Done()
|
||||
break
|
||||
}
|
||||
|
||||
out <- value
|
||||
}
|
||||
}(in)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(out)
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
62
part_7/7.10/fan-out.go
Normal file
62
part_7/7.10/fan-out.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type TaskItem struct {
|
||||
id int
|
||||
}
|
||||
|
||||
func main() {
|
||||
tasks := []TaskItem{
|
||||
{0}, {1}, {2},
|
||||
{3}, {4}, {5},
|
||||
{6}, {7}, {8},
|
||||
{9}, {10},
|
||||
}
|
||||
|
||||
in := generateInput(&tasks)
|
||||
|
||||
out1 := fanOut(in)
|
||||
out2 := fanOut(in)
|
||||
out3 := fanOut(in)
|
||||
|
||||
for range tasks {
|
||||
select {
|
||||
case value := <-out1:
|
||||
fmt.Println("Task in output-1: ", value)
|
||||
case value := <-out2:
|
||||
fmt.Println("Task in output-2: ", value)
|
||||
case value := <-out3:
|
||||
fmt.Println("Task in output-3: ", value)
|
||||
}
|
||||
}
|
||||
fmt.Println("Finished")
|
||||
}
|
||||
|
||||
func generateInput(work *[]TaskItem) (in1 <-chan TaskItem) {
|
||||
ch1 := make(chan TaskItem)
|
||||
go func() {
|
||||
defer close(ch1)
|
||||
for _, value := range *work {
|
||||
ch1 <- value
|
||||
}
|
||||
}()
|
||||
|
||||
return ch1
|
||||
}
|
||||
|
||||
func fanOut(in <-chan TaskItem) <-chan TaskItem {
|
||||
out := make(chan TaskItem)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
for data := range in {
|
||||
out <- data
|
||||
}
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
20
part_7/7.10/generator.go
Normal file
20
part_7/7.10/generator.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Generator(start int, end int) <-chan int {
|
||||
ch := make(chan int, end-start)
|
||||
go func(ch chan int) {
|
||||
for i := start; i <= end; i++ {
|
||||
ch <- i // помещение значения в канал
|
||||
}
|
||||
close(ch)
|
||||
}(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
func main() {
|
||||
for it := range Generator(1, 10) {
|
||||
fmt.Printf("%d || ", it)
|
||||
}
|
||||
}
|
||||
62
part_7/7.10/pipeline.go
Normal file
62
part_7/7.10/pipeline.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
func main() {
|
||||
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
||||
|
||||
out := source(slice) // первый блок конвейера
|
||||
out = square(out) // блок возведения в квадрат
|
||||
out = decriment(out) // блок декремента значения
|
||||
|
||||
for value := range out { // вывод результата работы конвейера в терминал
|
||||
fmt.Printf("%d ", value)
|
||||
}
|
||||
}
|
||||
|
||||
func source(in []int) <-chan int {
|
||||
out := make(chan int)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
for _, it := range in {
|
||||
if it%3 != 0 {
|
||||
out <- it
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func square(in <-chan int) <-chan int {
|
||||
out := make(chan int)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
for i := range in {
|
||||
value := math.Pow(float64(i), 2)
|
||||
out <- int(value)
|
||||
}
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func decriment(in <-chan int) <-chan int {
|
||||
out := make(chan int)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
for i := range in {
|
||||
out <- i - 1
|
||||
}
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
44
part_7/7.10/queuing.go
Normal file
44
part_7/7.10/queuing.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TaskItem struct {
|
||||
id int
|
||||
}
|
||||
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
limit := make(chan interface{}, 2)
|
||||
workers := func(l chan<- interface{}, wg *sync.WaitGroup, tasks *[]TaskItem) {
|
||||
for _, value := range *tasks {
|
||||
value := value // удалите и посмотрите на вывод в терминал
|
||||
// не используйте в замыкании передачу задачи по ссылке на переменную value
|
||||
// объявленную в цикле (_, value := range *tasks)
|
||||
|
||||
limit <- struct{}{}
|
||||
wg.Add(1)
|
||||
|
||||
go func(workItem *TaskItem, w *sync.WaitGroup) {
|
||||
defer w.Done()
|
||||
fmt.Printf("Task %d processing\n", workItem.id)
|
||||
time.Sleep(1 * time.Second)
|
||||
<-limit
|
||||
}(&value, wg)
|
||||
}
|
||||
}
|
||||
|
||||
tasks := []TaskItem{
|
||||
{0}, {1}, {2},
|
||||
{3}, {4}, {5},
|
||||
{6}, {7}, {8},
|
||||
{9},
|
||||
}
|
||||
workers(limit, &wg, &tasks)
|
||||
wg.Wait()
|
||||
|
||||
fmt.Println("Finished")
|
||||
}
|
||||
58
part_7/7.10/workerpool.go
Normal file
58
part_7/7.10/workerpool.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const totalElements = 6
|
||||
const totalWorkers = 3
|
||||
|
||||
type TaskItem struct {
|
||||
id int
|
||||
data int
|
||||
}
|
||||
|
||||
func main() {
|
||||
input := make(chan TaskItem, totalElements)
|
||||
output := make(chan TaskItem, totalElements)
|
||||
|
||||
for w := 1; w <= totalWorkers; w++ {
|
||||
go worker(w, input, output)
|
||||
}
|
||||
|
||||
// Отправка данных для обработки
|
||||
for j := 0; j < totalElements; j++ {
|
||||
input <- TaskItem{j, j}
|
||||
}
|
||||
|
||||
close(input)
|
||||
|
||||
// Работа с результатами
|
||||
for a := 0; a < totalElements; a++ {
|
||||
task := <-output
|
||||
fmt.Printf("Task-%d finished!\n", task.id)
|
||||
}
|
||||
|
||||
close(output)
|
||||
}
|
||||
|
||||
func worker(id int, input <-chan TaskItem, output chan<- TaskItem) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for j := range input {
|
||||
wg.Add(1)
|
||||
|
||||
go func(task TaskItem) {
|
||||
defer wg.Done()
|
||||
|
||||
fmt.Printf("Worker %d started task %+v\n", id, task)
|
||||
task.data *= task.data
|
||||
output <- task
|
||||
|
||||
fmt.Printf("Worker %d finished task %+v\n", id, task)
|
||||
}(j)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
18
part_7/7.3/1.go
Normal file
18
part_7/7.3/1.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func namedGorutine() {
|
||||
for i := 0; i <= 5; i++ {
|
||||
fmt.Printf("%d ", i)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go namedGorutine() // объявление горутины
|
||||
go func() {
|
||||
for i := 6; i <= 10; i++ {
|
||||
fmt.Printf("%d", i)
|
||||
}
|
||||
}()
|
||||
}
|
||||
22
part_7/7.3/2.go
Normal file
22
part_7/7.3/2.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func namedGorutine() {
|
||||
for i := 0; i <= 5; i++ {
|
||||
fmt.Printf("%d ", i)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go namedGorutine() // объявление горутины
|
||||
go func() {
|
||||
for i := 6; i <= 10; i++ {
|
||||
fmt.Printf("%d", i)
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
17
part_7/7.3/3.go
Normal file
17
part_7/7.3/3.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func namedGorutine(number int) {
|
||||
fmt.Printf("%d ", number)
|
||||
}
|
||||
|
||||
func main() {
|
||||
for i := 0; i <= 30; i++ {
|
||||
go namedGorutine(i)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
16
part_7/7.3/4.go
Normal file
16
part_7/7.3/4.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func namedGorutine(number int) int{
|
||||
fmt.Printf("%d ", number)
|
||||
return number
|
||||
}
|
||||
|
||||
func main() {
|
||||
number := go namedGorutine(1)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
21
part_7/7.4/1.go
Normal file
21
part_7/7.4/1.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func namedGorutine(number int, waitGroup *sync.WaitGroup) {
|
||||
defer waitGroup.Done() // уменьшение счетчика sync.WaitGroup на 1
|
||||
fmt.Printf("%d", number)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for i := 0; i <= 30; i++ {
|
||||
waitGroup.Add(1) // увеличение счетчика на 1
|
||||
go namedGorutine(i, &waitGroup)
|
||||
}
|
||||
waitGroup.Wait() // ожидание завершения всех запущенных горутин
|
||||
}
|
||||
26
part_7/7.4/2.go
Normal file
26
part_7/7.4/2.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func namedGorutine(number int, waitGroup *sync.WaitGroup) {
|
||||
defer waitGroup.Done() // уменьшение счетчика sync.WaitGroup на 1
|
||||
fmt.Printf("%d ", number)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
waitGroup.Add(1) // приведет к ошибке!
|
||||
fmt.Printf("%#v\n", waitGroup)
|
||||
|
||||
for i := 0; i <= 30; i++ {
|
||||
waitGroup.Add(1) // увеличение счетчика на 1
|
||||
go namedGorutine(i, &waitGroup)
|
||||
}
|
||||
|
||||
fmt.Printf("\n%#v\n", waitGroup)
|
||||
waitGroup.Wait()
|
||||
}
|
||||
26
part_7/7.4/3.go
Normal file
26
part_7/7.4/3.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func namedGorutine(number int, waitGroup *sync.WaitGroup) {
|
||||
defer waitGroup.Done() // уменьшение счетчика sync.WaitGroup на 1
|
||||
fmt.Printf("%d ", number)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
fmt.Printf("%#v\n", waitGroup)
|
||||
waitGroup.Done() // приведет к панике!
|
||||
for i := 0; i <= 30; i++ {
|
||||
waitGroup.Add(1) // увеличение счетчика на 1
|
||||
go namedGorutine(i, &waitGroup)
|
||||
|
||||
}
|
||||
|
||||
fmt.Printf("\n%#v\n", waitGroup)
|
||||
waitGroup.Wait()
|
||||
}
|
||||
31
part_7/7.5/1.go
Normal file
31
part_7/7.5/1.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
func myPrint(c chan int) {
|
||||
value, ok := <-c // чтение из канала. Горутина блокируется до тех пор,
|
||||
// пока не появятся данные для чтения
|
||||
if ok {
|
||||
fmt.Println("Channel is open!")
|
||||
} else {
|
||||
fmt.Println("Channel is closed!")
|
||||
}
|
||||
fmt.Println(value, ok)
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
myChannel := make(chan int) // объявление канала из 1-го элемента
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
go myPrint(myChannel)
|
||||
waitGroup.Add(1)
|
||||
time.Sleep(time.Second)
|
||||
myChannel <- 10 // запись значения 10 в канал
|
||||
waitGroup.Wait()
|
||||
}
|
||||
34
part_7/7.5/10.go
Normal file
34
part_7/7.5/10.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ping := make(chan int, 1)
|
||||
pong := make(chan int, 1)
|
||||
|
||||
ping <- 1
|
||||
|
||||
go play(ping, pong)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("Exit")
|
||||
}
|
||||
|
||||
func play(ping, pong chan int) {
|
||||
var ball int
|
||||
for {
|
||||
select {
|
||||
case ball = <-ping:
|
||||
fmt.Println("Ping", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
pong <- ball + 1
|
||||
case ball = <-pong:
|
||||
fmt.Println("Pong", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
ping <- ball + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
35
part_7/7.5/11.go
Normal file
35
part_7/7.5/11.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ping := make(chan int, 1)
|
||||
pong := make(chan int, 1)
|
||||
|
||||
// ping <- 1
|
||||
|
||||
go func(ping, pong chan int) {
|
||||
var ball int
|
||||
for {
|
||||
select {
|
||||
case ball = <-ping:
|
||||
fmt.Println("Ping", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
pong <- ball + 1
|
||||
case ball = <-pong:
|
||||
fmt.Println("Pong", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
ping <- ball + 1
|
||||
case value := <-time.After(time.Second):
|
||||
fmt.Println("Time out!!!", value)
|
||||
return
|
||||
}
|
||||
}
|
||||
}(ping, pong)
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
fmt.Println("Exit")
|
||||
}
|
||||
35
part_7/7.5/12.go
Normal file
35
part_7/7.5/12.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ping := make(chan int, 1)
|
||||
pong := make(chan int, 1)
|
||||
|
||||
// ping <- 1
|
||||
|
||||
go func(ping, pong chan int) {
|
||||
var ball int
|
||||
for {
|
||||
select {
|
||||
case ball = <-ping:
|
||||
fmt.Println("Ping", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
pong <- ball + 1
|
||||
case ball = <-pong:
|
||||
fmt.Println("Pong", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
ping <- ball + 1
|
||||
default:
|
||||
fmt.Println("Time out!!!")
|
||||
return
|
||||
}
|
||||
}
|
||||
}(ping, pong)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("Exit")
|
||||
}
|
||||
40
part_7/7.5/13.go
Normal file
40
part_7/7.5/13.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ping := make(chan int, 1)
|
||||
pong := make(chan int, 1)
|
||||
|
||||
ping <- 1
|
||||
|
||||
go func(ping, pong chan int) {
|
||||
timeChan := make(chan time.Time)
|
||||
go func(timeChan chan time.Time) {
|
||||
timeChan <- (<-time.After(time.Second * 2))
|
||||
}(timeChan)
|
||||
|
||||
var ball int
|
||||
for {
|
||||
select {
|
||||
case ball = <-ping:
|
||||
fmt.Println("Ping", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
pong <- ball + 1
|
||||
case ball = <-pong:
|
||||
fmt.Println("Pong", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
ping <- ball + 1
|
||||
case <-timeChan:
|
||||
fmt.Println("Exit")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}(ping, pong)
|
||||
|
||||
select {} // вечное ожидание
|
||||
}
|
||||
38
part_7/7.5/14.go
Normal file
38
part_7/7.5/14.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ping := make(chan int, 1)
|
||||
pong := make(chan int, 1)
|
||||
|
||||
ping <- 1
|
||||
|
||||
go func(ping, pong chan int) {
|
||||
var ball int
|
||||
for {
|
||||
select {
|
||||
case ball = <-ping:
|
||||
fmt.Println("Ping", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if ball > 2 {
|
||||
ping = nil
|
||||
} else {
|
||||
pong <- ball + 1
|
||||
}
|
||||
case ball = <-pong:
|
||||
fmt.Println("Pong", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
ping <- ball + 1
|
||||
case value := <-time.After(time.Second):
|
||||
fmt.Println("Time out!!!", value)
|
||||
}
|
||||
}
|
||||
}(ping, pong)
|
||||
|
||||
time.Sleep(4 * time.Second)
|
||||
fmt.Println("Exit")
|
||||
}
|
||||
39
part_7/7.5/15.go
Normal file
39
part_7/7.5/15.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Rectangle struct {
|
||||
Width uint
|
||||
Length uint
|
||||
}
|
||||
|
||||
func (r *Rectangle) GetPerimeter() uint {
|
||||
return (r.Length + r.Width) * 2
|
||||
}
|
||||
|
||||
func (r *Rectangle) GetArea() uint {
|
||||
return r.Length * r.Width
|
||||
}
|
||||
|
||||
func calculate(in <-chan Rectangle, out chan<- float64) {
|
||||
var sum float64
|
||||
for i := range in {
|
||||
sum += float64(i.GetPerimeter())
|
||||
}
|
||||
out <- sum / float64(cap(in))
|
||||
}
|
||||
|
||||
func main() {
|
||||
inChan := make(chan Rectangle, 3)
|
||||
outChan := make(chan float64)
|
||||
go calculate(inChan, outChan)
|
||||
|
||||
inChan <- Rectangle{14, 6}
|
||||
inChan <- Rectangle{5, 21}
|
||||
close(inChan) // закрытие канала
|
||||
|
||||
result := <-outChan
|
||||
fmt.Printf("Result = %v", result)
|
||||
}
|
||||
24
part_7/7.5/2.go
Normal file
24
part_7/7.5/2.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func myPrint(c chan int) {
|
||||
for i := 0; i < 3; i++ {
|
||||
value := <-c
|
||||
fmt.Printf("%d ", value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
// объявление буферизированного канала
|
||||
myChannel := make(chan int, 3)
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
go myPrint(myChannel)
|
||||
myChannel <- 3
|
||||
myChannel <- 10 // запись значения 10 в канал
|
||||
myChannel <- 77
|
||||
fmt.Printf("\nExit ")
|
||||
}
|
||||
25
part_7/7.5/3.go
Normal file
25
part_7/7.5/3.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func myPrint(c chan int) {
|
||||
for i := 0; i < 3; i++ {
|
||||
value := <-c
|
||||
fmt.Printf("%d ", value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
myChannel := make(chan int, 3) // объявление буферизированного канала
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
go myPrint(myChannel)
|
||||
myChannel <- 3
|
||||
myChannel <- 10 // запись значения 10 в канал
|
||||
myChannel <- 77
|
||||
myChannel <- 105
|
||||
myChannel <- 104
|
||||
fmt.Printf("\nExit")
|
||||
}
|
||||
23
part_7/7.5/4.go
Normal file
23
part_7/7.5/4.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func myPrint(c chan int) {
|
||||
for i := 0; i < 3; i++ {
|
||||
value := <-c
|
||||
fmt.Printf("%d ", value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
myChannel := make(chan int, 3) // объявление буферизированного канала
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
go myPrint(myChannel)
|
||||
for i := 0; i < 10; i++ {
|
||||
myChannel <- i
|
||||
}
|
||||
fmt.Printf("\nExit")
|
||||
}
|
||||
27
part_7/7.5/5.go
Normal file
27
part_7/7.5/5.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func onlyRead(c <-chan int) { //только для чтения
|
||||
for i := 0; i <= 3; i++ {
|
||||
value := <-c
|
||||
fmt.Printf("%d ", value)
|
||||
}
|
||||
}
|
||||
|
||||
func onlyWrite(c chan<- int) { //только для записи
|
||||
for i := 0; i <= 3; i++ {
|
||||
c <- i
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
myChannel := make(chan int, 3) // объявление буферизированного канала
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
go onlyRead(myChannel)
|
||||
go onlyWrite(myChannel)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
28
part_7/7.5/6.go
Normal file
28
part_7/7.5/6.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func onlyRead(c <-chan int) { //только для чтения
|
||||
c <- 4
|
||||
for i := 0; i <= 3; i++ {
|
||||
value := <-c
|
||||
fmt.Printf("%d ", value)
|
||||
}
|
||||
}
|
||||
|
||||
func onlyWrite(c chan<- int) { //только для записи
|
||||
for i := 0; i <= 3; i++ {
|
||||
c <- i
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
myChannel := make(chan int, 3) // объявление буферизированного канала
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
go onlyRead(myChannel)
|
||||
go onlyWrite(myChannel)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
23
part_7/7.5/7.go
Normal file
23
part_7/7.5/7.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func infoChan(c chan int) {
|
||||
length, capacity := len(c), cap(c)
|
||||
fmt.Printf("Length = %d, capacity = %d\n", length, capacity)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var value int
|
||||
myChannel := make(chan int, 3) // объявление буферизированного канала
|
||||
defer close(myChannel) // отложенное закрытие канала
|
||||
infoChan(myChannel)
|
||||
myChannel <- 1
|
||||
myChannel <- 2
|
||||
infoChan(myChannel)
|
||||
value = <-myChannel
|
||||
infoChan(myChannel)
|
||||
fmt.Println(value)
|
||||
}
|
||||
41
part_7/7.5/8.go
Normal file
41
part_7/7.5/8.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Rectangle struct {
|
||||
Width uint
|
||||
Length uint
|
||||
}
|
||||
|
||||
func (r *Rectangle) GetPerimeter() uint {
|
||||
return (r.Length + r.Width) * 2
|
||||
}
|
||||
|
||||
func (r *Rectangle) GetArea() uint {
|
||||
return r.Length * r.Width
|
||||
}
|
||||
|
||||
func calculate(in <-chan Rectangle, out chan<- float64) {
|
||||
var sum float64
|
||||
for i := 0; i <= 3; i++ {
|
||||
reactangle := <-in
|
||||
sum += float64(reactangle.GetPerimeter())
|
||||
}
|
||||
out <- sum / float64(cap(in))
|
||||
}
|
||||
|
||||
func main() {
|
||||
inChan := make(chan Rectangle, 3)
|
||||
outChan := make(chan float64)
|
||||
go calculate(inChan, outChan)
|
||||
|
||||
inChan <- Rectangle{14, 6}
|
||||
inChan <- Rectangle{5, 21}
|
||||
inChan <- Rectangle{10, 33}
|
||||
inChan <- Rectangle{2, 5}
|
||||
|
||||
result := <-outChan
|
||||
fmt.Printf("Result = %v", result)
|
||||
}
|
||||
41
part_7/7.5/9.go
Normal file
41
part_7/7.5/9.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pingChan := make(chan int, 1)
|
||||
pongChan := make(chan int, 1)
|
||||
|
||||
go ping(pingChan, pongChan)
|
||||
go pong(pongChan, pingChan)
|
||||
|
||||
pingChan <- 1
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("Exit")
|
||||
}
|
||||
|
||||
func ping(pingChan <-chan int, pongChan chan<- int) {
|
||||
for {
|
||||
ball := <-pingChan
|
||||
|
||||
fmt.Println("Ping", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
pongChan <- ball + 1
|
||||
}
|
||||
}
|
||||
|
||||
func pong(pongChan <-chan int, pingChan chan<- int) {
|
||||
for {
|
||||
ball := <-pongChan
|
||||
|
||||
fmt.Println("Pong", ball)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
pingChan <- ball + 1
|
||||
}
|
||||
}
|
||||
23
part_7/7.6/1.go
Normal file
23
part_7/7.6/1.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var value = 0
|
||||
|
||||
func increment(waitGroup *sync.WaitGroup) {
|
||||
value = value + 1
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
for i := 0; i < 1000; i++ {
|
||||
waitGroup.Add(1)
|
||||
go increment(&waitGroup)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Println("Value after 1k increment = ", value)
|
||||
}
|
||||
26
part_7/7.7/1.go
Normal file
26
part_7/7.7/1.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var value int
|
||||
|
||||
func increment(waitGroup *sync.WaitGroup, c chan bool) {
|
||||
c <- true // блокируем критический раздел
|
||||
value = value + 1
|
||||
<-c // разблокировка критического раздела
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
myChan := make(chan bool, 1)
|
||||
for i := 0; i < 1000; i++ {
|
||||
waitGroup.Add(1)
|
||||
go increment(&waitGroup, myChan)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Println("Value after 1k increment = ", value)
|
||||
}
|
||||
26
part_7/7.7/2.go
Normal file
26
part_7/7.7/2.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var value int
|
||||
|
||||
func increment(waitGroup *sync.WaitGroup, mutex *sync.Mutex) {
|
||||
mutex.Lock() // блокируем критический раздел
|
||||
defer mutex.Unlock() // отложенная разблокировка
|
||||
value = value + 1
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
mutex := new(sync.Mutex) // либо var mutex sync.Mutex
|
||||
for i := 0; i < 1000; i++ {
|
||||
waitGroup.Add(1)
|
||||
go increment(&waitGroup, mutex)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Println("Value after 1k increment = ", value)
|
||||
}
|
||||
94
part_7/7.7/3.go
Normal file
94
part_7/7.7/3.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const count = 100
|
||||
|
||||
type Person struct {
|
||||
ID uint64
|
||||
Name string
|
||||
Age uint8
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
id uint64
|
||||
owner Person
|
||||
balance int
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *Account) WithdrawMoney(amount int) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.balance -= amount
|
||||
}
|
||||
|
||||
func (a *Account) DepositMoney(amount int) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.balance += amount
|
||||
}
|
||||
|
||||
func (a *Account) GetBalance() int {
|
||||
a.lock.RLock()
|
||||
defer a.lock.RUnlock()
|
||||
return a.balance
|
||||
}
|
||||
|
||||
func NewAccount(id uint64, owner Person, balance int) *Account {
|
||||
return &Account{
|
||||
id: 123213123123,
|
||||
owner: owner,
|
||||
balance: balance,
|
||||
}
|
||||
}
|
||||
|
||||
func WithdrawMoney(waitGroup *sync.WaitGroup, account *Account) {
|
||||
for i := 0; i < count; i++ {
|
||||
account.WithdrawMoney(i)
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func DepositMoney(waitGroup *sync.WaitGroup, account *Account) {
|
||||
for i := 0; i < count; i++ {
|
||||
account.DepositMoney(i)
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func PrintBalance(number int, account *Account) {
|
||||
for {
|
||||
fmt.Printf("%d)Current account balance = %d\n",
|
||||
number, account.GetBalance())
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
account := NewAccount(
|
||||
324234234,
|
||||
Person{
|
||||
ID: 233424,
|
||||
Name: "Alex",
|
||||
Age: 21,
|
||||
},
|
||||
30000,
|
||||
)
|
||||
waitGroup.Add(2)
|
||||
go WithdrawMoney(&waitGroup, account)
|
||||
go DepositMoney(&waitGroup, account)
|
||||
for i := 0; i <= 3; i++ {
|
||||
go PrintBalance(i, account)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Printf("Final account balance = %d", account.GetBalance())
|
||||
}
|
||||
128
part_7/7.7/4.go
Normal file
128
part_7/7.7/4.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Количество философов и вилок
|
||||
const numPhilosophers = 5
|
||||
|
||||
// Философ представляет участника задачи
|
||||
type Philosopher struct {
|
||||
id int
|
||||
leftFork *sync.Mutex
|
||||
rightFork *sync.Mutex
|
||||
mealsEaten int
|
||||
thinkingTime time.Duration
|
||||
eatingTime time.Duration
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// Функция приема пищи философом
|
||||
// Пытается взять две вилки и затем есть
|
||||
func (p *Philosopher) eat() {
|
||||
defer p.wg.Done()
|
||||
|
||||
// Количество приемов пищи для каждого философа
|
||||
for i := 0; i < 3; i++ {
|
||||
// Думаем некоторое время перед тем как пытаться взять вилки
|
||||
fmt.Printf("Philosopher %d is thinking... 🤔\n", p.id)
|
||||
time.Sleep(p.thinkingTime)
|
||||
|
||||
// Решение проблемы взаимной блокировки:
|
||||
// Нечетные философы сначала берут левую вилку, четные - правую
|
||||
if p.id%2 == 0 {
|
||||
// Четные философы: сначала правая, потом левая вилка
|
||||
p.rightFork.Lock()
|
||||
fmt.Printf("Philosopher %d picked up right fork 🍴\n", p.id)
|
||||
// Небольшая задержка для наглядности
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
p.leftFork.Lock()
|
||||
fmt.Printf(
|
||||
"Philosopher %d picked up left fork and started eating\n",
|
||||
p.id,
|
||||
)
|
||||
} else {
|
||||
// Нечетные философы: сначала левая, потом правая вилка
|
||||
p.leftFork.Lock()
|
||||
fmt.Printf("Philosopher %d picked up left fork 🍴😊\n", p.id)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
p.rightFork.Lock()
|
||||
fmt.Printf(
|
||||
"Philosopher %d picked up right fork and started eating 🍴😊\n",
|
||||
p.id,
|
||||
)
|
||||
}
|
||||
|
||||
// Прием пищи
|
||||
fmt.Printf("Philosopher %d is eating... 🍽️😋\n", p.id)
|
||||
time.Sleep(p.eatingTime)
|
||||
p.mealsEaten++
|
||||
|
||||
// Кладем вилки обратно на стол
|
||||
p.leftFork.Unlock()
|
||||
p.rightFork.Unlock()
|
||||
fmt.Printf(
|
||||
"Philosopher %d put down forks and finished eating (%d times) ✓\n",
|
||||
p.id, p.mealsEaten,
|
||||
)
|
||||
}
|
||||
|
||||
fmt.Printf("Philosopher %d finished dining and left the table 👋\n", p.id)
|
||||
}
|
||||
|
||||
func main() {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// Создаем группу ожидания для синхронизации горутин
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Создаем вилки (мьютексы)
|
||||
forks := make([]*sync.Mutex, numPhilosophers)
|
||||
for i := 0; i < numPhilosophers; i++ {
|
||||
forks[i] = &sync.Mutex{}
|
||||
}
|
||||
|
||||
// Создаем философов
|
||||
philosophers := make([]*Philosopher, numPhilosophers)
|
||||
for i := 0; i < numPhilosophers; i++ {
|
||||
// Каждый философ имеет доступ к вилке слева и справа
|
||||
leftForkIndex := i
|
||||
rightForkIndex := (i + 1) % numPhilosophers
|
||||
thinkingTime := time.Duration(rnd.Intn(500)+100) * time.Millisecond
|
||||
eatingTime := time.Duration(rnd.Intn(300)+200) * time.Millisecond
|
||||
// Создаем философа с случайным временем размышления и приема пищи
|
||||
philosophers[i] = &Philosopher{
|
||||
id: i,
|
||||
leftFork: forks[leftForkIndex],
|
||||
rightFork: forks[rightForkIndex],
|
||||
mealsEaten: 0,
|
||||
thinkingTime: thinkingTime,
|
||||
eatingTime: eatingTime,
|
||||
wg: &wg,
|
||||
}
|
||||
|
||||
// Добавляем философа в группу ожидания
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
// Запускаем философов в отдельных горутинах
|
||||
fmt.Println("Philosophers are sitting at the table... 🪑🪑🪑")
|
||||
for i := 0; i < numPhilosophers; i++ {
|
||||
go philosophers[i].eat()
|
||||
}
|
||||
|
||||
// Ожидаем завершения трапезы всех философов
|
||||
wg.Wait()
|
||||
fmt.Println("All philosophers have finished dining! 🎉")
|
||||
|
||||
// Выводим статистику по приемам пищи
|
||||
for i, p := range philosophers {
|
||||
fmt.Printf("Philosopher %d ate %d times 🍽️\n", i, p.mealsEaten)
|
||||
}
|
||||
}
|
||||
24
part_7/7.8/1.go
Normal file
24
part_7/7.8/1.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var value int64 = 0
|
||||
|
||||
func increment(waitGroup *sync.WaitGroup) {
|
||||
atomic.AddInt64(&value, 1)
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
for i := 0; i < 1000; i++ {
|
||||
waitGroup.Add(1)
|
||||
go increment(&waitGroup)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Println("Value after 1k increment = ", value)
|
||||
}
|
||||
39
part_7/7.8/2.go
Normal file
39
part_7/7.8/2.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type AtomicCounter struct {
|
||||
value int64
|
||||
}
|
||||
|
||||
func (a *AtomicCounter) Increment() {
|
||||
atomic.AddInt64(&a.value, 1)
|
||||
}
|
||||
|
||||
func (a *AtomicCounter) Decrement() {
|
||||
atomic.AddInt64(&a.value, -1)
|
||||
}
|
||||
|
||||
func (a *AtomicCounter) Value() int64 {
|
||||
return atomic.LoadInt64(&a.value)
|
||||
}
|
||||
|
||||
func increment(waitGroup *sync.WaitGroup, counter *AtomicCounter) {
|
||||
counter.Increment()
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
counter := &AtomicCounter{0}
|
||||
for i := 0; i < 1000; i++ {
|
||||
waitGroup.Add(1)
|
||||
go increment(&waitGroup, counter)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Println("Value after 1k increment = ", counter.Value())
|
||||
}
|
||||
15
part_7/7.9/1.go
Normal file
15
part_7/7.9/1.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func getGOMAXPROCS() int {
|
||||
return runtime.GOMAXPROCS(0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("GOMAXPROCS: %d\n", runtime.NumCPU())
|
||||
// NumCPU возвращает количество логических CPU, используемых текущим процессом.
|
||||
}
|
||||
47
part_7/7.9/2.go
Normal file
47
part_7/7.9/2.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AtomicCounter struct {
|
||||
value int64
|
||||
}
|
||||
|
||||
func (a *AtomicCounter) Increment() {
|
||||
atomic.AddInt64(&a.value, 1)
|
||||
}
|
||||
|
||||
func (a *AtomicCounter) Decrement() {
|
||||
atomic.AddInt64(&a.value, -1)
|
||||
}
|
||||
|
||||
func (a *AtomicCounter) Value() int64 {
|
||||
return atomic.LoadInt64(&a.value)
|
||||
}
|
||||
|
||||
func increment(waitGroup *sync.WaitGroup, counter *AtomicCounter) {
|
||||
counter.Increment()
|
||||
waitGroup.Done()
|
||||
}
|
||||
|
||||
func getGOMAXPROCS() int {
|
||||
return runtime.GOMAXPROCS(0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var waitGroup sync.WaitGroup
|
||||
counter := &AtomicCounter{0}
|
||||
myTime := time.Now()
|
||||
for i := 0; i < 100_000; i++ {
|
||||
waitGroup.Add(1)
|
||||
go increment(&waitGroup, counter)
|
||||
}
|
||||
waitGroup.Wait()
|
||||
fmt.Println("Value after 1k increment = ", counter.Value())
|
||||
fmt.Printf("Time work: %v", time.Since(myTime))
|
||||
}
|
||||
14
part_7/tic_tac_toe_v6/.vscode/launch.json
vendored
Normal file
14
part_7/tic_tac_toe_v6/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}", // <- ставим запятую
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
192
part_7/tic_tac_toe_v6/board/board.go
Normal file
192
part_7/tic_tac_toe_v6/board/board.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package board
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
BoardDefaultSize int = 3
|
||||
BoardMinSize int = 3
|
||||
BoardMaxSize int = 9
|
||||
)
|
||||
|
||||
type Board struct {
|
||||
Board [][]BoardField `json:"board"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
func NewBoard(size int) *Board {
|
||||
board := make([][]BoardField, size)
|
||||
for i := range board {
|
||||
board[i] = make([]BoardField, size)
|
||||
}
|
||||
return &Board{Board: board, Size: size}
|
||||
}
|
||||
|
||||
// Отображение игрового поля
|
||||
func (b *Board) PrintBoard() {
|
||||
fmt.Print(" ")
|
||||
for i := range b.Size {
|
||||
fmt.Printf("%d ", i+1)
|
||||
}
|
||||
fmt.Println()
|
||||
for i := range b.Size {
|
||||
fmt.Printf("%d ", i+1)
|
||||
for j := range b.Size {
|
||||
switch b.Board[i][j] {
|
||||
case Empty:
|
||||
fmt.Print(". ")
|
||||
case Cross:
|
||||
fmt.Print("X ")
|
||||
case Nought:
|
||||
fmt.Print("O ")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка возможности и выполнения хода
|
||||
func (b *Board) makeMove(x, y int) bool {
|
||||
return b.Board[x][y] == Empty
|
||||
}
|
||||
|
||||
func (b *Board) SetSymbol(x, y int, player BoardField) bool {
|
||||
if b.makeMove(x, y) {
|
||||
b.Board[x][y] = player
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Проверка выигрыша
|
||||
func (b *Board) CheckWin(player BoardField) bool {
|
||||
if b.Size <= 4 {
|
||||
// Для маленьких досок используем обычную проверку
|
||||
return b.checkWinSequential(player)
|
||||
}
|
||||
|
||||
// Для больших досок используем параллельную проверку
|
||||
|
||||
// 3 направления проверок: строки/столбцы, 2 диагонали
|
||||
resultChan := make(chan bool, 3)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
|
||||
// Параллельная проверка строк и столбцов
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := range b.Size {
|
||||
rowWin, colWin := true, true
|
||||
for j := 0; j < b.Size; j++ {
|
||||
if b.Board[i][j] != player {
|
||||
rowWin = false
|
||||
}
|
||||
if b.Board[j][i] != player {
|
||||
colWin = false
|
||||
}
|
||||
}
|
||||
if rowWin || colWin {
|
||||
resultChan <- true
|
||||
return // Нашли выигрыш, выходим из горутины
|
||||
}
|
||||
}
|
||||
resultChan <- false
|
||||
}()
|
||||
|
||||
// Параллельная проверка главной диагонали
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mainDiag := true
|
||||
for i := range b.Size {
|
||||
if b.Board[i][i] != player {
|
||||
mainDiag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
resultChan <- mainDiag
|
||||
}()
|
||||
|
||||
// Параллельная проверка побочной диагонали
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
antiDiag := true
|
||||
for i := range b.Size {
|
||||
if b.Board[i][b.Size-i-1] != player {
|
||||
antiDiag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
resultChan <- antiDiag
|
||||
}()
|
||||
|
||||
// Запускаем горутину, которая закроет канал после завершения всех проверок
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
}()
|
||||
|
||||
// Получаем результаты проверок с помощью for range.
|
||||
// Этот цикл будет ждать, пока канал не будет закрыт.
|
||||
for result := range resultChan {
|
||||
if result {
|
||||
return true // Найден выигрыш.
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Оригинальный алгоритм проверки выигрыша для малых досок
|
||||
func (b *Board) checkWinSequential(player BoardField) bool {
|
||||
// Проверка строк и столбцов
|
||||
for i := range b.Size {
|
||||
rowWin, colWin := true, true
|
||||
for j := range b.Size {
|
||||
if b.Board[i][j] != player {
|
||||
rowWin = false
|
||||
}
|
||||
if b.Board[j][i] != player {
|
||||
colWin = false
|
||||
}
|
||||
}
|
||||
if rowWin || colWin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Главная диагональ
|
||||
mainDiag := true
|
||||
for i := range b.Size {
|
||||
if b.Board[i][i] != player {
|
||||
mainDiag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if mainDiag {
|
||||
return true
|
||||
}
|
||||
|
||||
// Побочная диагональ
|
||||
antiDiag := true
|
||||
for i := range b.Size {
|
||||
if b.Board[i][b.Size-i-1] != player {
|
||||
antiDiag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return antiDiag
|
||||
}
|
||||
|
||||
// Проверка на ничью
|
||||
func (b *Board) CheckDraw() bool {
|
||||
for i := range b.Size {
|
||||
for j := range b.Size {
|
||||
if b.Board[i][j] == Empty {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
10
part_7/tic_tac_toe_v6/board/board_cell_type.go
Normal file
10
part_7/tic_tac_toe_v6/board/board_cell_type.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package board
|
||||
|
||||
type BoardField int
|
||||
|
||||
// фигуры в клетке поля
|
||||
const (
|
||||
Empty BoardField = iota
|
||||
Cross
|
||||
Nought
|
||||
)
|
||||
137
part_7/tic_tac_toe_v6/database/crud.go
Normal file
137
part_7/tic_tac_toe_v6/database/crud.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
m "tic-tac-toe/model"
|
||||
)
|
||||
|
||||
func (r *SQLiteRepository) createPlayer(
|
||||
nickName string,
|
||||
) (*Player, error) {
|
||||
player := &Player{NickName: nickName}
|
||||
if err := r.db.Create(player).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return player, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) getPlayer(
|
||||
nickName string,
|
||||
) (*Player, error) {
|
||||
var player Player
|
||||
if err := r.db.Where(
|
||||
"nick_name = ?", nickName,
|
||||
).First(&player).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &player, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) SaveSnapshot(
|
||||
snapshot *m.GameSnapshot,
|
||||
playerNickName string,
|
||||
) error {
|
||||
player, _ := r.getPlayer(playerNickName)
|
||||
if player == nil {
|
||||
player, _ = r.createPlayer(playerNickName)
|
||||
}
|
||||
|
||||
boardJSON, err := json.Marshal(snapshot.Board)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.db.Create(&GameSnapshot{
|
||||
SnapshotName: snapshot.SnapshotName,
|
||||
BoardJSON: boardJSON,
|
||||
PlayerFigure: int(snapshot.PlayerFigure),
|
||||
State: int(snapshot.State),
|
||||
Mode: int(snapshot.Mode),
|
||||
Difficulty: int(snapshot.Difficulty),
|
||||
IsCurrentFirst: snapshot.IsCurrentFirst,
|
||||
PlayerNickName: player.NickName,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) GetSnapshots(
|
||||
nickName string) (*[]m.GameSnapshot, error) {
|
||||
var snapshots []GameSnapshot
|
||||
// ищем игрока по никнейму
|
||||
player, err := r.getPlayer(nickName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// находим все снапшоты игрока
|
||||
if err := r.db.Where(
|
||||
"player_nick_name = ?", player.NickName,
|
||||
).Find(&snapshots).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var gameSnapshots []m.GameSnapshot
|
||||
for _, snapshot := range snapshots {
|
||||
temp, err := snapshot.ToModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gameSnapshots = append(gameSnapshots, *temp)
|
||||
}
|
||||
return &gameSnapshots, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) IsSnapshotExist(
|
||||
snapshotName string, nickName string,
|
||||
) (bool, error) {
|
||||
var snapshot GameSnapshot
|
||||
if err := r.db.Where(
|
||||
"snapshot_name = ? AND player_nick_name = ?",
|
||||
snapshotName, nickName,
|
||||
).First(&snapshot).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) SaveFinishedGame(
|
||||
snapshot *m.FinishGameSnapshot) error {
|
||||
boardJSON, err := json.Marshal(snapshot.Board)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
player, _ := r.getPlayer(snapshot.PlayerNickName)
|
||||
if player == nil {
|
||||
player, _ = r.createPlayer(snapshot.PlayerNickName)
|
||||
}
|
||||
|
||||
return r.db.Create(&PlayerFinishGame{
|
||||
BoardJSON: boardJSON,
|
||||
PlayerFigure: int(snapshot.PlayerFigure),
|
||||
WinnerName: snapshot.WinnerName,
|
||||
PlayerNickName: player.NickName,
|
||||
Time: snapshot.Time,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) GetFinishedGames(
|
||||
nickName string,
|
||||
) (*[]m.FinishGameSnapshot, error) {
|
||||
var playerFinishGames []PlayerFinishGame
|
||||
if err := r.db.Where(
|
||||
"player_nick_name = ?", nickName,
|
||||
).Find(&playerFinishGames).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var finishGameSnapshots []m.FinishGameSnapshot
|
||||
for _, playerFinishGame := range playerFinishGames {
|
||||
temp, err := playerFinishGame.ToModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finishGameSnapshots = append(finishGameSnapshots, *temp)
|
||||
}
|
||||
return &finishGameSnapshots, nil
|
||||
}
|
||||
45
part_7/tic_tac_toe_v6/database/database.go
Normal file
45
part_7/tic_tac_toe_v6/database/database.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SQLiteRepository struct {
|
||||
db *gorm.DB // заменили на *gorm.DB
|
||||
}
|
||||
|
||||
func NewSQLiteRepository() (*SQLiteRepository, error) {
|
||||
// Создаем репозиторий
|
||||
repository := &SQLiteRepository{}
|
||||
|
||||
// Проверяем существование файла базы данных
|
||||
dbExists := true
|
||||
if _, err := os.Stat(dbName); os.IsNotExist(err) {
|
||||
dbExists = false
|
||||
}
|
||||
|
||||
// Открываем соединение с базой данных
|
||||
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Сохраняем соединение в репозитории
|
||||
repository.db = db
|
||||
|
||||
// Если база данных только что создана, выполняем миграцию
|
||||
if !dbExists {
|
||||
fmt.Println("Creating new database schema")
|
||||
if err := db.AutoMigrate(&Player{}, &GameSnapshot{}, &PlayerFinishGame{}); err != nil {
|
||||
return nil, fmt.Errorf("failed to migrate database: %w", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Using existing database")
|
||||
}
|
||||
|
||||
return repository, nil
|
||||
}
|
||||
12
part_7/tic_tac_toe_v6/database/db_definition.go
Normal file
12
part_7/tic_tac_toe_v6/database/db_definition.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package database
|
||||
|
||||
import "errors"
|
||||
|
||||
const dbName = "tic_tac_toe.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_7/tic_tac_toe_v6/database/i_repository.go
Normal file
17
part_7/tic_tac_toe_v6/database/i_repository.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package database
|
||||
|
||||
import "tic-tac-toe/model"
|
||||
|
||||
// Интерфейс для работы с базой данных
|
||||
type IRepository interface {
|
||||
// Сохраняет снапшот игры для указанного игрока
|
||||
SaveSnapshot(snapshot *model.GameSnapshot, playerNickName string) error
|
||||
// Получает все снапшоты игр для указанного игрока
|
||||
GetSnapshots(nickName string) (*[]model.GameSnapshot, error)
|
||||
// Проверяет существует ли снапшот с указанным именем для данного игрока
|
||||
IsSnapshotExist(snapshotName string, nickName string) (bool, error)
|
||||
// Сохраняет информацию о завершенной игре
|
||||
SaveFinishedGame(snapshot *model.FinishGameSnapshot) error
|
||||
// Получает все завершенные игры для указанного игрока
|
||||
GetFinishedGames(nickName string) (*[]model.FinishGameSnapshot, error)
|
||||
}
|
||||
36
part_7/tic_tac_toe_v6/database/models.go
Normal file
36
part_7/tic_tac_toe_v6/database/models.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package database
|
||||
|
||||
import "time"
|
||||
|
||||
// Player представляет модель таблицы
|
||||
// для хранения профилей игроков в БД
|
||||
type Player struct {
|
||||
NickName string `gorm:"primary_key;not null"`
|
||||
}
|
||||
|
||||
// PlayerFinishGame представляет модель таблицы
|
||||
// для хранения завершенной игры в БД
|
||||
type PlayerFinishGame struct {
|
||||
ID int `gorm:"primary_key;autoIncrement;not null"`
|
||||
WinnerName string `gorm:"not null"`
|
||||
BoardJSON []byte `gorm:"type:json;not null"`
|
||||
PlayerFigure int `gorm:"not null"`
|
||||
Time time.Time `gorm:"not null"`
|
||||
PlayerNickName string `gorm:"not null"`
|
||||
Player *Player `gorm:"foreignKey:PlayerNickName;references:NickName"`
|
||||
}
|
||||
|
||||
// GameSnapshot представляет модель таблицы
|
||||
// для хранения снапшота игры в БД
|
||||
type GameSnapshot struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement;not null"`
|
||||
SnapshotName string `gorm:"not null"`
|
||||
BoardJSON []byte `gorm:"type:json;not null"`
|
||||
PlayerFigure int `gorm:"not null"`
|
||||
State int `gorm:"not null"`
|
||||
Mode int `gorm:"not null"`
|
||||
Difficulty int `gorm:"not null"`
|
||||
IsCurrentFirst bool `gorm:"not null"`
|
||||
PlayerNickName string `gorm:"not null"`
|
||||
Player *Player `gorm:"foreignKey:PlayerNickName;references:NickName"`
|
||||
}
|
||||
60
part_7/tic_tac_toe_v6/database/utils.go
Normal file
60
part_7/tic_tac_toe_v6/database/utils.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
b "tic-tac-toe/board"
|
||||
m "tic-tac-toe/model"
|
||||
p "tic-tac-toe/player"
|
||||
)
|
||||
|
||||
// Задаем имя таблицы для структуры Player
|
||||
func (p *Player) TableName() string {
|
||||
return "players"
|
||||
}
|
||||
|
||||
// Задаем имя таблицы для структуры 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{
|
||||
Board: &board,
|
||||
PlayerFigure: b.BoardField(f.PlayerFigure),
|
||||
WinnerName: f.WinnerName,
|
||||
PlayerNickName: f.PlayerNickName,
|
||||
Time: f.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Задаем имя таблицы для структуры GameSnapshot
|
||||
func (g *GameSnapshot) TableName() string {
|
||||
return "game_snapshots"
|
||||
}
|
||||
|
||||
// Преобразуем таблицу GameSnapshot в модель GameSnapshot
|
||||
// из пакета model
|
||||
func (gs *GameSnapshot) ToModel() (*m.GameSnapshot, error) {
|
||||
var board b.Board
|
||||
if err := json.Unmarshal(gs.BoardJSON, &board); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &m.GameSnapshot{
|
||||
Board: &board,
|
||||
PlayerFigure: b.BoardField(gs.PlayerFigure),
|
||||
State: gs.State,
|
||||
Mode: gs.Mode,
|
||||
Difficulty: p.Difficulty(gs.Difficulty),
|
||||
IsCurrentFirst: gs.IsCurrentFirst,
|
||||
SnapshotName: gs.SnapshotName,
|
||||
}, nil
|
||||
}
|
||||
83
part_7/tic_tac_toe_v6/game/game_core.go
Normal file
83
part_7/tic_tac_toe_v6/game/game_core.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
b "tic-tac-toe/board"
|
||||
db "tic-tac-toe/database"
|
||||
p "tic-tac-toe/player"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Board *b.Board `json:"board"`
|
||||
Player p.IPlayer `json:"player"`
|
||||
// Не сериализуется напрямую
|
||||
Player2 p.IPlayer `json:"-"`
|
||||
// Не сериализуется напрямую
|
||||
CurrentPlayer p.IPlayer `json:"-"`
|
||||
Reader *bufio.Reader `json:"-"`
|
||||
State GameState `json:"state"`
|
||||
repository db.IRepository `json:"-"`
|
||||
// Режим игры (PvP или PvC)
|
||||
Mode GameMode `json:"mode"`
|
||||
// Уровень сложности компьютера (только для PvC)
|
||||
Difficulty p.Difficulty `json:"difficulty,omitempty"`
|
||||
// Флаг для определения текущего игрока
|
||||
IsCurrentFirst bool `json:"is_current_first"`
|
||||
}
|
||||
|
||||
// Создаем новую игру
|
||||
func NewGame(
|
||||
board b.Board, reader *bufio.Reader,
|
||||
repository db.IRepository,
|
||||
mode GameMode, difficulty p.Difficulty,
|
||||
) *Game {
|
||||
// Создаем первого игрока (всегда человек на X)
|
||||
player1 := p.NewHumanPlayer(b.Cross, reader)
|
||||
|
||||
var player2 p.IPlayer
|
||||
if mode == PlayerVsPlayer {
|
||||
// Для режима игрок против игрока создаем второго человека-игрока
|
||||
player2 = p.NewHumanPlayer(b.Nought, reader)
|
||||
} else {
|
||||
// Для режима игрок против компьютера создаем компьютерного игрока
|
||||
player2 = p.NewComputerPlayer(b.Nought, difficulty)
|
||||
}
|
||||
|
||||
return &Game{
|
||||
Board: &board,
|
||||
Player: player1,
|
||||
Player2: player2,
|
||||
CurrentPlayer: player1,
|
||||
Reader: reader,
|
||||
State: playing,
|
||||
repository: repository,
|
||||
Mode: mode,
|
||||
Difficulty: difficulty,
|
||||
IsCurrentFirst: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Переключаем активного игрока
|
||||
func (g *Game) switchCurrentPlayer() {
|
||||
if g.CurrentPlayer == g.Player {
|
||||
g.CurrentPlayer = g.Player2
|
||||
} else {
|
||||
g.CurrentPlayer = g.Player
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем состояние игры
|
||||
func (g *Game) updateState() {
|
||||
if g.Board.CheckWin(g.CurrentPlayer.GetFigure()) {
|
||||
if g.CurrentPlayer.GetFigure() == b.Cross {
|
||||
g.State = crossWin
|
||||
} else {
|
||||
g.State = noughtWin
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if g.Board.CheckDraw() {
|
||||
g.State = draw
|
||||
}
|
||||
}
|
||||
181
part_7/tic_tac_toe_v6/game/game_play.go
Normal file
181
part_7/tic_tac_toe_v6/game/game_play.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tic-tac-toe/model"
|
||||
p "tic-tac-toe/player"
|
||||
)
|
||||
|
||||
func (g *Game) Play() {
|
||||
fmt.Println("For saving the game enter: save filename")
|
||||
fmt.Println("For exiting the game enter : q")
|
||||
fmt.Println("For making a move enter: row col")
|
||||
|
||||
for g.State == playing {
|
||||
g.Board.PrintBoard()
|
||||
|
||||
// Определяем, кто делает ход: человек или компьютер
|
||||
if g.Mode == PlayerVsComputer && g.CurrentPlayer == g.Player2 {
|
||||
// Если ход компьютера, просто вызываем его MakeMove
|
||||
fmt.Println("Computer is making a move...")
|
||||
row, col, _ := g.CurrentPlayer.MakeMove(g.Board)
|
||||
|
||||
// Применяем ход компьютера к доске
|
||||
g.Board.SetSymbol(row, col, g.CurrentPlayer.GetFigure())
|
||||
} else {
|
||||
fmt.Printf(
|
||||
"%s's turn. Enter row and column (e.g. 1 2): ",
|
||||
g.CurrentPlayer.GetSymbol(),
|
||||
)
|
||||
|
||||
// Читаем ввод пользователя
|
||||
input, _ := g.Reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// Проверка выхода из игры
|
||||
if input == "q" {
|
||||
g.State = quit
|
||||
break
|
||||
}
|
||||
|
||||
// Проверка и выполнение сохранения игры
|
||||
if g.saveCheck(input) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Получаем ход человека-игрока через парсинг ввода
|
||||
hPlayer, ok := g.CurrentPlayer.(*p.HumanPlayer)
|
||||
if !ok {
|
||||
fmt.Println("Invalide data. Please try again!")
|
||||
continue
|
||||
}
|
||||
|
||||
// Парсим ввод и получаем координаты хода
|
||||
row, col, validMove := hPlayer.ParseMove(input, g.Board)
|
||||
if !validMove {
|
||||
fmt.Println("Invalide data. Please try again!")
|
||||
continue
|
||||
}
|
||||
|
||||
// Устанавливаем символ на доску
|
||||
if !g.Board.SetSymbol(row, col, hPlayer.GetFigure()) {
|
||||
fmt.Println("This cell is already occupied!")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем состояние игры
|
||||
g.updateState()
|
||||
|
||||
// Если игра продолжается, меняем игрока
|
||||
if g.State == playing {
|
||||
g.switchCurrentPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
// Печатаем итоговую доску и результат
|
||||
g.Board.PrintBoard()
|
||||
fmt.Println()
|
||||
|
||||
var winner string
|
||||
switch g.State {
|
||||
case crossWin:
|
||||
winner = "X"
|
||||
fmt.Println("X wins!")
|
||||
case noughtWin:
|
||||
fmt.Println("O wins!")
|
||||
winner = "O"
|
||||
case draw:
|
||||
fmt.Println("It's a draw!")
|
||||
winner = "Draw"
|
||||
}
|
||||
|
||||
if winner != "" {
|
||||
g.saveFinishedGame(winner)
|
||||
}
|
||||
}
|
||||
|
||||
// Сохраняем результат завершенной игры
|
||||
func (g *Game) saveFinishedGame(winner string) {
|
||||
// Запрашиваем ник игрока
|
||||
fmt.Print("Enter your nickname to save the game result: ")
|
||||
nickName, _ := g.Reader.ReadString('\n')
|
||||
nickName = strings.TrimSpace(nickName)
|
||||
|
||||
if nickName == "" {
|
||||
fmt.Println("Nickname is empty, game result not saved.")
|
||||
return
|
||||
}
|
||||
|
||||
// Создаем снапшот
|
||||
finishSnapshot := &model.FinishGameSnapshot{
|
||||
Board: g.Board,
|
||||
PlayerFigure: g.CurrentPlayer.GetFigure(),
|
||||
WinnerName: winner,
|
||||
PlayerNickName: nickName,
|
||||
Time: time.Now(),
|
||||
}
|
||||
|
||||
// Асинхронно сохраняем результат игры
|
||||
go func(snapshot *model.FinishGameSnapshot) {
|
||||
err := g.repository.SaveFinishedGame(snapshot)
|
||||
if err != nil {
|
||||
// Логируем ошибку, но не блокируем пользовательский интерфейс
|
||||
fmt.Printf("Error saving game result: %v\n", err)
|
||||
}
|
||||
}(finishSnapshot)
|
||||
|
||||
fmt.Println("Saving game result in background...")
|
||||
}
|
||||
|
||||
// Проверяем, являются ли введенные данные командой на сохранение
|
||||
func (g *Game) saveCheck(input string) bool {
|
||||
// Проверяем, если пользователь ввел только "save" без имени файла
|
||||
if input == "save" {
|
||||
fmt.Println("Error: missing filename. " +
|
||||
"Please use the format: save filename")
|
||||
return false
|
||||
}
|
||||
|
||||
// Проверяем команду сохранения с именем файла
|
||||
if len(input) > 5 && input[:5] == "save " {
|
||||
filename := input[5:]
|
||||
|
||||
// Проверяем, что имя файла не пустое
|
||||
if len(strings.TrimSpace(filename)) == 0 {
|
||||
fmt.Println("Error: empty file name. " +
|
||||
"Please use the format: save filename")
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Print("Enter nickname: ")
|
||||
nickName, _ := g.Reader.ReadString('\n')
|
||||
nickName = strings.TrimSpace(nickName)
|
||||
|
||||
exist, _ := g.repository.IsSnapshotExist(filename, nickName)
|
||||
if exist {
|
||||
fmt.Println(
|
||||
"Snapshot already exists. Please choose another name.",
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
shapshot := g.gameSnapshot()
|
||||
shapshot.SnapshotName = filename
|
||||
|
||||
// Асинхронно сохраняем снапшот, чтобы не блокировать игру
|
||||
go func(s *model.GameSnapshot, n string) {
|
||||
if err := g.repository.SaveSnapshot(s, n); err != nil {
|
||||
// Выводим ошибку в консоль, если она произошла в фоне
|
||||
fmt.Printf("\nError saving snapshot in background: %v\n", err)
|
||||
}
|
||||
}(shapshot, nickName)
|
||||
|
||||
fmt.Println("Snapshot saving initiated in the background...")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
84
part_7/tic_tac_toe_v6/game/game_serialization.go
Normal file
84
part_7/tic_tac_toe_v6/game/game_serialization.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
b "tic-tac-toe/board"
|
||||
db "tic-tac-toe/database"
|
||||
m "tic-tac-toe/model"
|
||||
p "tic-tac-toe/player"
|
||||
)
|
||||
|
||||
// Подготавливаем игру к сохранению
|
||||
func (g *Game) prepareForSave() {
|
||||
// Устанавливаем флаг текущего игрока
|
||||
g.IsCurrentFirst = (g.CurrentPlayer == g.Player)
|
||||
}
|
||||
|
||||
// Возвращаем снапшот игровой сессии
|
||||
func (g *Game) gameSnapshot() *m.GameSnapshot {
|
||||
g.prepareForSave()
|
||||
return &m.GameSnapshot{
|
||||
Board: g.Board,
|
||||
PlayerFigure: g.Player.GetFigure(),
|
||||
State: int(g.State),
|
||||
Mode: int(g.Mode),
|
||||
Difficulty: g.Difficulty,
|
||||
IsCurrentFirst: g.IsCurrentFirst,
|
||||
}
|
||||
}
|
||||
|
||||
// Восстанавливаем игру из снапшота
|
||||
func (g *Game) RestoreFromSnapshot(
|
||||
snapshot *m.GameSnapshot,
|
||||
reader *bufio.Reader,
|
||||
repository db.IRepository,
|
||||
) {
|
||||
g.Board = snapshot.Board
|
||||
g.State = GameState(snapshot.State)
|
||||
g.Mode = GameMode(snapshot.Mode)
|
||||
g.Difficulty = p.Difficulty(snapshot.Difficulty)
|
||||
g.IsCurrentFirst = snapshot.IsCurrentFirst
|
||||
|
||||
// Создаем объекты игроков
|
||||
g.Player = &p.HumanPlayer{Figure: snapshot.PlayerFigure}
|
||||
|
||||
g.Reader = reader
|
||||
g.repository = repository
|
||||
|
||||
g.recreatePlayersAfterLoad(reader)
|
||||
}
|
||||
|
||||
// Восстанавливаем объекты игроков после загрузки снапшота
|
||||
func (g *Game) recreatePlayersAfterLoad(reader *bufio.Reader) {
|
||||
// Создаем игроков в зависимости от режима игры
|
||||
if g.Player == nil {
|
||||
fmt.Println("Error: Player is nil")
|
||||
return
|
||||
}
|
||||
|
||||
playerFigure := g.Player.GetFigure()
|
||||
g.Player = p.NewHumanPlayer(playerFigure, reader)
|
||||
|
||||
// Получаем фигуру второго игрока
|
||||
var player2Figure b.BoardField
|
||||
if playerFigure == b.Cross {
|
||||
player2Figure = b.Nought
|
||||
} else {
|
||||
player2Figure = b.Cross
|
||||
}
|
||||
|
||||
// Создаем второго игрока в зависимости от режима
|
||||
if g.Mode == PlayerVsPlayer {
|
||||
g.Player2 = p.NewHumanPlayer(player2Figure, reader)
|
||||
} else {
|
||||
g.Player2 = p.NewComputerPlayer(player2Figure, g.Difficulty)
|
||||
}
|
||||
|
||||
// Восстанавливаем указатель на текущего игрока
|
||||
if g.IsCurrentFirst {
|
||||
g.CurrentPlayer = g.Player
|
||||
} else {
|
||||
g.CurrentPlayer = g.Player2
|
||||
}
|
||||
}
|
||||
116
part_7/tic_tac_toe_v6/game/game_setup.go
Normal file
116
part_7/tic_tac_toe_v6/game/game_setup.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
b "tic-tac-toe/board"
|
||||
db "tic-tac-toe/database"
|
||||
p "tic-tac-toe/player"
|
||||
)
|
||||
|
||||
// Создаем новую игру с пользовательскими настройками
|
||||
func SetupGame(reader *bufio.Reader, repository db.IRepository) *Game {
|
||||
// Запрашиваем размер игрового поля
|
||||
size := getBoardSize(reader)
|
||||
|
||||
// Создаем доску
|
||||
board := *b.NewBoard(size)
|
||||
|
||||
// Запрашиваем режим игры
|
||||
mode := getGameMode(reader)
|
||||
|
||||
// Если выбран режим против компьютера, запрашиваем сложность
|
||||
var difficulty p.Difficulty
|
||||
if mode == PlayerVsComputer {
|
||||
difficulty = getDifficulty(reader)
|
||||
}
|
||||
|
||||
// Создаем новую игру
|
||||
return NewGame(board, reader, repository, mode, difficulty)
|
||||
}
|
||||
|
||||
// Запрашиваем у пользователя размер доски
|
||||
func getBoardSize(reader *bufio.Reader) int {
|
||||
size := b.BoardDefaultSize
|
||||
var err error
|
||||
for {
|
||||
fmt.Printf("Choose board size (min: %d, max: %d, default: %d): ",
|
||||
b.BoardMinSize, b.BoardMaxSize, b.BoardDefaultSize)
|
||||
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// Если пользователь не ввел ничего, используем размер по умолчанию
|
||||
if input == "" {
|
||||
return b.BoardDefaultSize
|
||||
}
|
||||
|
||||
// Пытаемся преобразовать ввод в число
|
||||
size, err = strconv.Atoi(input)
|
||||
if err != nil || size < b.BoardMinSize || size > b.BoardMaxSize {
|
||||
fmt.Println("Invalid input. Please try again!")
|
||||
continue
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
// Запрашиваем у пользователя режим игры
|
||||
func getGameMode(reader *bufio.Reader) GameMode {
|
||||
for {
|
||||
fmt.Println("Choose game mode:")
|
||||
fmt.Println("1 - Player vs Player (PvP)")
|
||||
fmt.Println("2 - Player vs Computer (PvC)")
|
||||
fmt.Print("Your choice: ")
|
||||
|
||||
input, err := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Invalid input. Please try again!")
|
||||
continue
|
||||
}
|
||||
|
||||
switch input {
|
||||
case "1":
|
||||
return PlayerVsPlayer
|
||||
case "2":
|
||||
return PlayerVsComputer
|
||||
default:
|
||||
fmt.Println("Invalid input. Please try again!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Запрашиваем у пользователя уровень сложности компьютера
|
||||
func getDifficulty(reader *bufio.Reader) p.Difficulty {
|
||||
for {
|
||||
fmt.Println("Choose computer difficulty:")
|
||||
fmt.Println("1 - Easy (random moves)")
|
||||
fmt.Println("2 - Medium (block winning moves)")
|
||||
fmt.Println("3 - Hard (optimal strategy)")
|
||||
fmt.Print("Your choice: ")
|
||||
|
||||
input, err := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Invalid input. Please try again!")
|
||||
continue
|
||||
}
|
||||
|
||||
switch input {
|
||||
case "1":
|
||||
return p.Easy
|
||||
case "2":
|
||||
return p.Medium
|
||||
case "3":
|
||||
return p.Hard
|
||||
default:
|
||||
fmt.Println("Invalid input. Please try again!")
|
||||
}
|
||||
}
|
||||
}
|
||||
20
part_7/tic_tac_toe_v6/game/game_state.go
Normal file
20
part_7/tic_tac_toe_v6/game/game_state.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package game
|
||||
|
||||
type GameState int
|
||||
|
||||
// состояние игрового процесса
|
||||
const (
|
||||
playing GameState = iota
|
||||
draw
|
||||
crossWin
|
||||
noughtWin
|
||||
quit
|
||||
)
|
||||
|
||||
// Режим игры
|
||||
type GameMode int
|
||||
|
||||
const (
|
||||
PlayerVsPlayer GameMode = iota
|
||||
PlayerVsComputer
|
||||
)
|
||||
16
part_7/tic_tac_toe_v6/go.mod
Normal file
16
part_7/tic_tac_toe_v6/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module tic-tac-toe
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
|
||||
require (
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
)
|
||||
12
part_7/tic_tac_toe_v6/go.sum
Normal file
12
part_7/tic_tac_toe_v6/go.sum
Normal file
@@ -0,0 +1,12 @@
|
||||
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.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
252
part_7/tic_tac_toe_v6/main.go
Normal file
252
part_7/tic_tac_toe_v6/main.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"tic-tac-toe/database"
|
||||
"tic-tac-toe/game"
|
||||
"tic-tac-toe/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Загрузка сохраненной игры
|
||||
func loadGame(reader *bufio.Reader, repository database.IRepository) {
|
||||
loadedGame := &game.Game{}
|
||||
|
||||
for {
|
||||
fmt.Print("Input your nickname: ")
|
||||
nickName, _ := reader.ReadString('\n')
|
||||
nickName = strings.TrimSpace(nickName)
|
||||
|
||||
// Используем горутину для загрузки снапшотов
|
||||
type snapshotResult struct {
|
||||
snapshots *[]model.GameSnapshot
|
||||
err error
|
||||
}
|
||||
snapshotChan := make(chan snapshotResult)
|
||||
|
||||
go func() {
|
||||
snapshots, err := repository.GetSnapshots(nickName)
|
||||
snapshotChan <- snapshotResult{snapshots, err}
|
||||
}()
|
||||
|
||||
// Пока загружаются снапшоты, можем показать индикатор загрузки
|
||||
fmt.Print("Loading saved games")
|
||||
for range 10 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Print(".")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Получаем результат
|
||||
result := <-snapshotChan
|
||||
if result.err != nil {
|
||||
fmt.Println("Error loading game: ", result.err)
|
||||
continue
|
||||
}
|
||||
snapshote := result.snapshots
|
||||
|
||||
// Выводим все снапшоты игрока
|
||||
fmt.Println("\n═══════════════════ SAVED GAMES ════════════════════")
|
||||
fmt.Println("┌────────┬────────────────┬─────────┬─────────┬────────────┐")
|
||||
fmt.Println("│ ID │ Name │ Figure │ Mode │ Difficulty │")
|
||||
fmt.Println("├────────┼────────────────┼─────────┼─────────┼────────────┤")
|
||||
|
||||
if len(*snapshote) == 0 {
|
||||
fmt.Println("│ │ No saved games found │")
|
||||
fmt.Println("└────────┴───────────────────────────────────────────────────┘")
|
||||
} else {
|
||||
for ID, snapshot := range *snapshote {
|
||||
// Конвертируем режим игры (0=PvP, 1=PvC) в читаемый текст
|
||||
gameMode := "PvP"
|
||||
if snapshot.Mode == 1 {
|
||||
gameMode = "PvC"
|
||||
}
|
||||
|
||||
// Конвертируем сложность (0=Easy, 1=Medium, 2=Hard) в читаемый текст
|
||||
difficulty := "-"
|
||||
if snapshot.Mode == 1 { // Только для режима PvC
|
||||
switch snapshot.Difficulty {
|
||||
case 0:
|
||||
difficulty = "Easy"
|
||||
case 1:
|
||||
difficulty = "Medium"
|
||||
case 2:
|
||||
difficulty = "Hard"
|
||||
}
|
||||
}
|
||||
|
||||
// Форматированный вывод с выравниванием колонок
|
||||
figure := "X"
|
||||
if snapshot.PlayerFigure == 1 {
|
||||
figure = "O"
|
||||
}
|
||||
|
||||
name := snapshot.SnapshotName
|
||||
if name == "" {
|
||||
name = "Game " + strconv.Itoa(ID)
|
||||
}
|
||||
|
||||
fmt.Printf("│ %-4d │ %-14s │ %-4s │ %-5s │ %-7s │\n",
|
||||
ID, name, figure, gameMode, difficulty)
|
||||
}
|
||||
fmt.Println("└────────┴────────────────┴─────────┴─────────┴────────────┘")
|
||||
}
|
||||
|
||||
// Запрашиваем номер снапшота
|
||||
snapID := -1
|
||||
for {
|
||||
fmt.Print("Enter snapshot number: ")
|
||||
num, _ := reader.ReadString('\n')
|
||||
num = strings.TrimSpace(num)
|
||||
|
||||
if snapID, _ = strconv.Atoi(num); snapID < 0 || snapID >= len(*snapshote) {
|
||||
fmt.Println("Invalid snapshot number. Please try again.")
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
// Восстанавливаем все необходимые поля игры
|
||||
loadedGame.RestoreFromSnapshot(
|
||||
&(*snapshote)[snapID], reader,
|
||||
repository,
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// Запускаем игру
|
||||
loadedGame.Play()
|
||||
}
|
||||
|
||||
// Показать все завершенную игру
|
||||
func showFinishedGames(
|
||||
reader *bufio.Reader, repository database.IRepository,
|
||||
) {
|
||||
fmt.Print("Enter nickname: ")
|
||||
nickName, _ := reader.ReadString('\n')
|
||||
nickName = strings.TrimSpace(nickName)
|
||||
|
||||
// Используем горутину для загрузки снапшотов
|
||||
type snapshotResult struct {
|
||||
snapshots *[]model.FinishGameSnapshot
|
||||
err error
|
||||
}
|
||||
snapshotChan := make(chan snapshotResult)
|
||||
|
||||
go func() {
|
||||
snapshots, err := repository.GetFinishedGames(nickName)
|
||||
snapshotChan <- snapshotResult{snapshots, err}
|
||||
}()
|
||||
|
||||
// Пока загружаются снапшоты, можем показать индикатор загрузки
|
||||
fmt.Print("Loading finished games")
|
||||
for range 10 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Print(".")
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Получаем результат
|
||||
result := <-snapshotChan
|
||||
if result.err != nil {
|
||||
fmt.Println("Error loading finished games: ", result.err)
|
||||
return
|
||||
}
|
||||
finishedGames := result.snapshots
|
||||
|
||||
fmt.Println("\n═══════════════════ FINISHED GAMES ════════════════════")
|
||||
fmt.Println("┌────────┬─────────┬────────────┬────────────────┐")
|
||||
fmt.Println("│ ID │ Figure │ Winner │ Date │")
|
||||
fmt.Println("├────────┼─────────┼────────────┼────────────────┤")
|
||||
|
||||
if len(*finishedGames) == 0 {
|
||||
fmt.Println("│ │ No finished games found │")
|
||||
fmt.Println("└────────┴───────────────────────────────────────────────┘")
|
||||
} else {
|
||||
for ID, game := range *finishedGames {
|
||||
// Форматированный вывод с выравниванием колонок
|
||||
figure := "X"
|
||||
if game.PlayerFigure == 1 {
|
||||
figure = "O"
|
||||
}
|
||||
|
||||
// Форматируем дату
|
||||
dateStr := game.Time.Format("02.01 15:04")
|
||||
|
||||
fmt.Printf("│ %-4d │ %-4s │ %-8s │ %-13s │\n",
|
||||
ID,
|
||||
figure,
|
||||
game.WinnerName,
|
||||
dateStr)
|
||||
}
|
||||
fmt.Println("└────────┴─────────┴────────────┴────────────────┘")
|
||||
}
|
||||
|
||||
// Запрашиваем номер игры
|
||||
snapID := -1
|
||||
for {
|
||||
fmt.Print("Enter snapshot number: ")
|
||||
num, _ := reader.ReadString('\n')
|
||||
num = strings.TrimSpace(num)
|
||||
|
||||
if snapID, _ = strconv.Atoi(num); snapID < 0 || snapID >= len(*finishedGames) {
|
||||
fmt.Println("Invalid snapshot number. Please try again.")
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Выводим выбранную игру
|
||||
chosenGame := (*finishedGames)[snapID]
|
||||
chosenGame.Board.PrintBoard()
|
||||
fmt.Println()
|
||||
fmt.Println("Winner: ", chosenGame.WinnerName)
|
||||
fmt.Println("Date: ", chosenGame.Time.Format("02.01.2006 15:04"))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func main() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
repository, err := database.NewSQLiteRepository()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating game storage: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
fmt.Println("Welcome to Tic-Tac-Toe!")
|
||||
fmt.Println("1 - Load game")
|
||||
fmt.Println("2 - New game")
|
||||
fmt.Println("3 - Show all finished games")
|
||||
fmt.Println("q - Exit")
|
||||
fmt.Print("Your choice: ")
|
||||
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
switch input {
|
||||
case "1": // Загрузка сохраненной игры
|
||||
loadGame(reader, repository)
|
||||
|
||||
case "2": // Создаем новую игру с помощью диалога настройки
|
||||
newGame := game.SetupGame(reader,
|
||||
repository)
|
||||
// Запускаем игру
|
||||
newGame.Play()
|
||||
|
||||
case "3": // Показать все завершенные игры
|
||||
showFinishedGames(reader, repository)
|
||||
|
||||
case "q":
|
||||
fmt.Println("Goodbye!")
|
||||
return
|
||||
|
||||
default:
|
||||
fmt.Println("Invalid choice. Please try again.")
|
||||
}
|
||||
}
|
||||
}
|
||||
14
part_7/tic_tac_toe_v6/model/finish_game_shapshot.go
Normal file
14
part_7/tic_tac_toe_v6/model/finish_game_shapshot.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"tic-tac-toe/board"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FinishGameSnapshot struct {
|
||||
Board *board.Board `json:"board"`
|
||||
PlayerFigure board.BoardField `json:"player_figure"`
|
||||
WinnerName string `json:"winner_name"`
|
||||
PlayerNickName string `json:"nick_name"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
17
part_7/tic_tac_toe_v6/model/game_snapshot.go
Normal file
17
part_7/tic_tac_toe_v6/model/game_snapshot.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
b "tic-tac-toe/board"
|
||||
p "tic-tac-toe/player"
|
||||
)
|
||||
|
||||
// Структура для сериализации/десериализации игры
|
||||
type GameSnapshot struct {
|
||||
SnapshotName string `json:"snapshot_name"`
|
||||
Board *b.Board `json:"board"`
|
||||
PlayerFigure b.BoardField `json:"player_figure"`
|
||||
State int `json:"state"`
|
||||
Mode int `json:"mode"`
|
||||
Difficulty p.Difficulty `json:"difficulty,omitempty"`
|
||||
IsCurrentFirst bool `json:"is_current_first"`
|
||||
}
|
||||
336
part_7/tic_tac_toe_v6/player/computer_player.go
Normal file
336
part_7/tic_tac_toe_v6/player/computer_player.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
b "tic-tac-toe/board"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Уровни сложности компьютера
|
||||
type Difficulty int
|
||||
|
||||
const (
|
||||
Easy Difficulty = iota
|
||||
Medium
|
||||
Hard
|
||||
)
|
||||
|
||||
// Структура для представления игрока-компьютера
|
||||
type ComputerPlayer struct {
|
||||
Figure b.BoardField `json:"figure"`
|
||||
Difficulty Difficulty `json:"difficulty"`
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
// Создаем нового игрока-компьютера с заданным уровнем сложности
|
||||
func NewComputerPlayer(
|
||||
figure b.BoardField,
|
||||
difficulty Difficulty,
|
||||
) *ComputerPlayer {
|
||||
source := rand.NewSource(time.Now().UnixNano())
|
||||
return &ComputerPlayer{
|
||||
Figure: figure,
|
||||
Difficulty: difficulty,
|
||||
rand: rand.New(source),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ComputerPlayer) GetSymbol() string {
|
||||
if p.Figure == b.Cross {
|
||||
return "X"
|
||||
}
|
||||
return "O"
|
||||
}
|
||||
|
||||
func (p *ComputerPlayer) SwitchPlayer() {
|
||||
if p.Figure == b.Cross {
|
||||
p.Figure = b.Nought
|
||||
} else {
|
||||
p.Figure = b.Cross
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ComputerPlayer) GetFigure() b.BoardField {
|
||||
return p.Figure
|
||||
}
|
||||
|
||||
func (p *ComputerPlayer) IsComputer() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Реализуем ход компьютера в зависимости от выбранной сложности
|
||||
func (p *ComputerPlayer) MakeMove(board *b.Board) (int, int, bool) {
|
||||
fmt.Printf("%s (Computer) making move... ", p.GetSymbol())
|
||||
|
||||
var row, col int
|
||||
switch p.Difficulty {
|
||||
case Easy:
|
||||
row, col = p.makeEasyMove(board)
|
||||
case Medium:
|
||||
row, col = p.makeMediumMove(board)
|
||||
case Hard:
|
||||
row, col = p.makeHardMove(board)
|
||||
}
|
||||
|
||||
fmt.Printf("Move made (%d, %d)\n", row+1, col+1)
|
||||
return row, col, true
|
||||
}
|
||||
|
||||
// Легкий уровень: случайный ход на свободную клетку
|
||||
func (p *ComputerPlayer) makeEasyMove(board *b.Board) (int, int) {
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
if len(emptyCells) == 0 {
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
// Выбираем случайную свободную клетку
|
||||
randomIndex := p.rand.Intn(len(emptyCells))
|
||||
return emptyCells[randomIndex][0], emptyCells[randomIndex][1]
|
||||
}
|
||||
|
||||
// Средний уровень: проверяет возможность выигрыша
|
||||
// или блокировки выигрыша противника
|
||||
func (p *ComputerPlayer) makeMediumMove(board *b.Board) (int, int) {
|
||||
// Проверяем, можем ли мы выиграть за один ход
|
||||
if move := p.findWinningMove(board, p.Figure); move != nil {
|
||||
return move[0], move[1]
|
||||
}
|
||||
|
||||
// Проверяем, нужно ли блокировать победу противника
|
||||
opponentFigure := b.Cross
|
||||
if p.Figure == b.Cross {
|
||||
opponentFigure = b.Nought
|
||||
}
|
||||
|
||||
if move := p.findWinningMove(board, opponentFigure); move != nil {
|
||||
return move[0], move[1]
|
||||
}
|
||||
|
||||
// Занимаем центр, если свободен (хорошая стратегия)
|
||||
center := board.Size / 2
|
||||
if board.Board[center][center] == b.Empty {
|
||||
return center, center
|
||||
}
|
||||
|
||||
// Занимаем угол, если свободен
|
||||
corners := [][]int{
|
||||
{0, 0},
|
||||
{0, board.Size - 1},
|
||||
{board.Size - 1, 0},
|
||||
{board.Size - 1, board.Size - 1},
|
||||
}
|
||||
|
||||
for _, corner := range corners {
|
||||
if board.Board[corner[0]][corner[1]] == b.Empty {
|
||||
return corner[0], corner[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Если нет лучшего хода, делаем случайный ход
|
||||
return p.makeEasyMove(board)
|
||||
}
|
||||
|
||||
// Сложный уровень: использует алгоритм минимакс для оптимального хода
|
||||
func (p *ComputerPlayer) makeHardMove(board *b.Board) (int, int) {
|
||||
// Если доска пустая, ходим в центр или угол (оптимальный первый ход)
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
if len(emptyCells) == board.Size*board.Size {
|
||||
// Первый ход - центр или угол
|
||||
center := board.Size / 2
|
||||
return center, center
|
||||
}
|
||||
|
||||
// Используем минимакс для доски 3x3
|
||||
// Для больших досок это слишком ресурсоемко
|
||||
if board.Size <= 3 {
|
||||
bestScore := -1000
|
||||
bestMove := []int{-1, -1}
|
||||
|
||||
// Создаем канал для результатов
|
||||
type moveResult struct {
|
||||
move []int
|
||||
score int
|
||||
}
|
||||
resultChan := make(chan moveResult, len(emptyCells))
|
||||
|
||||
// Запускаем горутину для каждого возможного хода
|
||||
for _, cell := range emptyCells {
|
||||
go func(cell []int) {
|
||||
row, col := cell[0], cell[1]
|
||||
// Копируем доску чтобы избежать гонок данных
|
||||
boardCopy := p.copyBoard(board)
|
||||
|
||||
// Пробуем сделать ход
|
||||
boardCopy.Board[row][col] = p.Figure
|
||||
|
||||
// Вычисляем оценку хода через минимакс
|
||||
score := p.minimax(boardCopy, 0, false)
|
||||
|
||||
// Отправляем результат в канал
|
||||
resultChan <- moveResult{
|
||||
move: []int{row, col},
|
||||
score: score,
|
||||
}
|
||||
}(cell)
|
||||
}
|
||||
|
||||
// Собираем результаты всех горутин
|
||||
for i := 0; i < len(emptyCells); i++ {
|
||||
result := <-resultChan
|
||||
if result.score > bestScore {
|
||||
bestScore = result.score
|
||||
bestMove = result.move
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove[0], bestMove[1]
|
||||
}
|
||||
|
||||
// Для больших досок выбираем случайно одну из трех параллельных стратегий
|
||||
strategyChoice := p.rand.Intn(3)
|
||||
switch strategyChoice {
|
||||
case 0:
|
||||
fmt.Println("Using limited-depth parallel minimax strategy")
|
||||
return p.makeLimitedDepthMinimax(board)
|
||||
case 1:
|
||||
fmt.Println("Using parallel heuristic evaluation strategy")
|
||||
return p.makeParallelHeuristicMove(board)
|
||||
case 2:
|
||||
fmt.Println("Using zone-based parallel analysis strategy")
|
||||
return p.makeZoneBasedMove(board)
|
||||
default:
|
||||
//В случае ошибки используем стратегию среднего уровня
|
||||
return p.makeMediumMove(board)
|
||||
}
|
||||
}
|
||||
|
||||
// Алгоритм минимакс для определения оптимального хода
|
||||
func (p *ComputerPlayer) minimax(
|
||||
board *b.Board,
|
||||
depth int, isMaximizing bool,
|
||||
) int {
|
||||
opponentFigure := b.Cross
|
||||
if p.Figure == b.Cross {
|
||||
opponentFigure = b.Nought
|
||||
}
|
||||
|
||||
// Проверяем терминальное состояние
|
||||
if board.CheckWin(p.Figure) {
|
||||
return 10 - depth // Выигрыш, чем быстрее, тем лучше
|
||||
} else if board.CheckWin(opponentFigure) {
|
||||
return depth - 10 // Проигрыш, чем дольше, тем лучше
|
||||
} else if board.CheckDraw() {
|
||||
return 0 // Ничья
|
||||
}
|
||||
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
|
||||
if isMaximizing {
|
||||
bestScore := -1000
|
||||
|
||||
// Проходим по всем свободным клеткам
|
||||
for _, cell := range emptyCells {
|
||||
row, col := cell[0], cell[1]
|
||||
|
||||
// Делаем ход
|
||||
board.Board[row][col] = p.Figure
|
||||
|
||||
// Рекурсивно оцениваем ход
|
||||
score := p.minimax(board, depth+1, false)
|
||||
|
||||
// Отменяем ход
|
||||
board.Board[row][col] = b.Empty
|
||||
|
||||
bestScore = max(score, bestScore)
|
||||
}
|
||||
|
||||
return bestScore
|
||||
} else {
|
||||
bestScore := 1000
|
||||
|
||||
// Проходим по всем свободным клеткам
|
||||
for _, cell := range emptyCells {
|
||||
row, col := cell[0], cell[1]
|
||||
|
||||
// Делаем ход противника
|
||||
board.Board[row][col] = opponentFigure
|
||||
|
||||
// Рекурсивно оцениваем ход
|
||||
score := p.minimax(board, depth+1, true)
|
||||
|
||||
// Отменяем ход
|
||||
board.Board[row][col] = b.Empty
|
||||
|
||||
bestScore = min(score, bestScore)
|
||||
}
|
||||
|
||||
return bestScore
|
||||
}
|
||||
}
|
||||
|
||||
// Вспомогательная функция для поиска хода, приводящего к выигрышу
|
||||
func (p *ComputerPlayer) findWinningMove(
|
||||
board *b.Board,
|
||||
figure b.BoardField,
|
||||
) []int {
|
||||
for _, cell := range p.getEmptyCells(board) {
|
||||
row, col := cell[0], cell[1]
|
||||
|
||||
// Пробуем сделать ход
|
||||
board.Board[row][col] = figure
|
||||
|
||||
// Проверяем, приведет ли этот ход к выигрышу
|
||||
if board.CheckWin(figure) {
|
||||
// Отменяем ход и возвращаем координаты
|
||||
board.Board[row][col] = b.Empty
|
||||
return []int{row, col}
|
||||
}
|
||||
|
||||
// Отменяем ход
|
||||
board.Board[row][col] = b.Empty
|
||||
}
|
||||
|
||||
return nil // Нет выигрышного хода
|
||||
}
|
||||
|
||||
// Получение списка пустых клеток
|
||||
func (p *ComputerPlayer) getEmptyCells(board *b.Board) [][]int {
|
||||
var emptyCells [][]int
|
||||
|
||||
for i := 0; i < board.Size; i++ {
|
||||
for j := 0; j < board.Size; j++ {
|
||||
if board.Board[i][j] == b.Empty {
|
||||
emptyCells = append(emptyCells, []int{i, j})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyCells
|
||||
}
|
||||
|
||||
// Вспомогательные функции max и min
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Копирование доски для избежания гонок данных при параллельном вычислении
|
||||
func (p *ComputerPlayer) copyBoard(board *b.Board) *b.Board {
|
||||
newBoard := b.NewBoard(board.Size)
|
||||
for i := 0; i < board.Size; i++ {
|
||||
for j := 0; j < board.Size; j++ {
|
||||
newBoard.Board[i][j] = board.Board[i][j]
|
||||
}
|
||||
}
|
||||
return newBoard
|
||||
}
|
||||
22
part_7/tic_tac_toe_v6/player/i_player.go
Normal file
22
part_7/tic_tac_toe_v6/player/i_player.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package player
|
||||
|
||||
import b "tic-tac-toe/board"
|
||||
|
||||
// Интерфейс для любого игрока, будь то человек или компьютер
|
||||
type IPlayer interface {
|
||||
// Получение символа игрока (X или O)
|
||||
GetSymbol() string
|
||||
|
||||
// Переключение хода на другого игрока
|
||||
SwitchPlayer()
|
||||
|
||||
// Получение текущей фигуры игрока
|
||||
GetFigure() b.BoardField
|
||||
|
||||
// Выполнение хода игрока
|
||||
// Возвращает координаты хода (x, y) и признак успешности
|
||||
MakeMove(board *b.Board) (int, int, bool)
|
||||
|
||||
// Проверка, является ли игрок компьютером
|
||||
IsComputer() bool
|
||||
}
|
||||
81
part_7/tic_tac_toe_v6/player/player.go
Normal file
81
part_7/tic_tac_toe_v6/player/player.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
b "tic-tac-toe/board"
|
||||
)
|
||||
|
||||
// Структура для представления игрока-человека
|
||||
type HumanPlayer struct {
|
||||
Figure b.BoardField `json:"figure"`
|
||||
Reader *bufio.Reader `json:"-"`
|
||||
}
|
||||
|
||||
func NewHumanPlayer(
|
||||
figure b.BoardField,
|
||||
reader *bufio.Reader,
|
||||
) *HumanPlayer {
|
||||
return &HumanPlayer{Figure: figure, Reader: reader}
|
||||
}
|
||||
|
||||
// Возвращаем символ игрока
|
||||
func (p *HumanPlayer) GetSymbol() string {
|
||||
if p.Figure == b.Cross {
|
||||
return "X"
|
||||
}
|
||||
return "O"
|
||||
}
|
||||
|
||||
// Изменяем фигуру текущего игрока
|
||||
func (p *HumanPlayer) SwitchPlayer() {
|
||||
if p.Figure == b.Cross {
|
||||
p.Figure = b.Nought
|
||||
} else {
|
||||
p.Figure = b.Cross
|
||||
}
|
||||
}
|
||||
|
||||
// Возвращаем текущую фигуру игрока
|
||||
func (p *HumanPlayer) GetFigure() b.BoardField {
|
||||
return p.Figure
|
||||
}
|
||||
|
||||
// Метод-заглушка, т.к. ввод игрока осуществляется на
|
||||
// уровне пакета 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
|
||||
}
|
||||
169
part_7/tic_tac_toe_v6/player/strategy_heuristic.go
Normal file
169
part_7/tic_tac_toe_v6/player/strategy_heuristic.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"sync"
|
||||
b "tic-tac-toe/board"
|
||||
)
|
||||
|
||||
// Метод запуска параллельной эвристической оценки ходов
|
||||
func (p *ComputerPlayer) makeParallelHeuristicMove(board *b.Board) (int, int) {
|
||||
bestScore := -100000
|
||||
var bestMove []int
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
|
||||
if len(emptyCells) == 0 {
|
||||
return -1, -1
|
||||
}
|
||||
if len(emptyCells) == 1 {
|
||||
return emptyCells[0][0], emptyCells[0][1]
|
||||
}
|
||||
|
||||
// Создаем канал для результатов
|
||||
type moveResult struct {
|
||||
move []int
|
||||
score int
|
||||
}
|
||||
resultChan := make(chan moveResult, len(emptyCells))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Запускаем горутины для каждого возможного хода
|
||||
for _, cell := range emptyCells {
|
||||
wg.Add(1)
|
||||
go func(r, c int) {
|
||||
defer wg.Done()
|
||||
boardCopy := p.copyBoard(board)
|
||||
boardCopy.Board[r][c] = p.Figure
|
||||
score := p.evaluateBoardHeuristic(boardCopy, p.Figure)
|
||||
resultChan <- moveResult{move: []int{r, c}, score: score}
|
||||
}(cell[0], cell[1])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
|
||||
// Определяем лучший ход
|
||||
for result := range resultChan {
|
||||
if result.score > bestScore {
|
||||
bestScore = result.score
|
||||
bestMove = result.move
|
||||
}
|
||||
}
|
||||
|
||||
if bestMove == nil {
|
||||
// Если по какой-то причине лучший ход не найден (маловероятно)
|
||||
// переходи на стратегию поведения среднего уровня сложности
|
||||
return p.makeMediumMove(board)
|
||||
}
|
||||
|
||||
return bestMove[0], bestMove[1]
|
||||
}
|
||||
|
||||
// Эвристическая оценка доски
|
||||
// Количество рядов, столбцов или диагоналей, где у игрока есть N фигур
|
||||
// и остальные клетки пусты. Также учитываем блокировку противника.
|
||||
func (p *ComputerPlayer) evaluateBoardHeuristic(
|
||||
board *b.Board, player b.BoardField,
|
||||
) int {
|
||||
score := 0
|
||||
opponent := b.Cross
|
||||
if player == b.Cross {
|
||||
opponent = b.Nought
|
||||
}
|
||||
|
||||
// Оценка за почти выигрышные линии для игрока
|
||||
// Почти выигрыш
|
||||
score += p.countPotentialLines(board, player, board.Size-1) * 100
|
||||
// Две фигуры в ряд (для Size > 2)
|
||||
score += p.countPotentialLines(board, player, board.Size-2) * 10
|
||||
|
||||
// Штраф за почти выигрышные линии для оппонента (блокировка)
|
||||
// Блокировка почти выигрыша оппонента
|
||||
score -= p.countPotentialLines(board, opponent, board.Size-1) * 90
|
||||
// Блокировка двух фигур оппонента
|
||||
score -= p.countPotentialLines(board, opponent, board.Size-2) * 5
|
||||
|
||||
// Бонус за занятие центра (особенно на нечетных досках)
|
||||
if board.Size%2 == 1 {
|
||||
center := board.Size / 2
|
||||
if board.Board[center][center] == player {
|
||||
score += 5
|
||||
} else if board.Board[center][center] == opponent {
|
||||
score -= 5
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// Вспомогательная функция для подсчета потенциальных линий
|
||||
func (p *ComputerPlayer) countPotentialLines(
|
||||
board *b.Board, player b.BoardField, numPlayerSymbols int,
|
||||
) int {
|
||||
count := 0
|
||||
lineSize := board.Size
|
||||
|
||||
// Проверка строк
|
||||
for r := 0; r < lineSize; r++ {
|
||||
playerSymbols := 0
|
||||
emptySymbols := 0
|
||||
for c := 0; c < lineSize; c++ {
|
||||
if board.Board[r][c] == player {
|
||||
playerSymbols++
|
||||
} else if board.Board[r][c] == b.Empty {
|
||||
emptySymbols++
|
||||
}
|
||||
}
|
||||
if playerSymbols == numPlayerSymbols &&
|
||||
(playerSymbols+emptySymbols) == lineSize {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка столбцов
|
||||
for c := 0; c < lineSize; c++ {
|
||||
playerSymbols := 0
|
||||
emptySymbols := 0
|
||||
for r := 0; r < lineSize; r++ {
|
||||
if board.Board[r][c] == player {
|
||||
playerSymbols++
|
||||
} else if board.Board[r][c] == b.Empty {
|
||||
emptySymbols++
|
||||
}
|
||||
}
|
||||
if playerSymbols == numPlayerSymbols &&
|
||||
(playerSymbols+emptySymbols) == lineSize {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка главной диагонали
|
||||
playerSymbolsDiag1 := 0
|
||||
emptySymbolsDiag1 := 0
|
||||
for i := 0; i < lineSize; i++ {
|
||||
if board.Board[i][i] == player {
|
||||
playerSymbolsDiag1++
|
||||
} else if board.Board[i][i] == b.Empty {
|
||||
emptySymbolsDiag1++
|
||||
}
|
||||
}
|
||||
if playerSymbolsDiag1 == numPlayerSymbols &&
|
||||
(playerSymbolsDiag1+emptySymbolsDiag1) == lineSize {
|
||||
count++
|
||||
}
|
||||
|
||||
// Проверка побочной диагонали
|
||||
playerSymbolsDiag2 := 0
|
||||
emptySymbolsDiag2 := 0
|
||||
for i := 0; i < lineSize; i++ {
|
||||
if board.Board[i][lineSize-1-i] == player {
|
||||
playerSymbolsDiag2++
|
||||
} else if board.Board[i][lineSize-1-i] == b.Empty {
|
||||
emptySymbolsDiag2++
|
||||
}
|
||||
}
|
||||
if playerSymbolsDiag2 == numPlayerSymbols &&
|
||||
(playerSymbolsDiag2+emptySymbolsDiag2) == lineSize {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
114
part_7/tic_tac_toe_v6/player/strategy_limited_minimax.go
Normal file
114
part_7/tic_tac_toe_v6/player/strategy_limited_minimax.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"sync"
|
||||
b "tic-tac-toe/board"
|
||||
)
|
||||
|
||||
const maxDepth = 2 // Ограничение глубины для минимакса
|
||||
|
||||
// Метод запуска стратегии с ограничением глубины для минимакса
|
||||
func (p *ComputerPlayer) makeLimitedDepthMinimax(board *b.Board) (int, int) {
|
||||
bestScore := -100000
|
||||
var bestMove []int
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
|
||||
if len(emptyCells) == 0 {
|
||||
return -1, -1 // Нет доступных ходов
|
||||
}
|
||||
if len(emptyCells) == 1 {
|
||||
return emptyCells[0][0], emptyCells[0][1] // Единственный возможный ход
|
||||
}
|
||||
|
||||
// Создаем канал для результатов
|
||||
type moveResult struct {
|
||||
move []int
|
||||
score int
|
||||
}
|
||||
resultChan := make(chan moveResult, len(emptyCells))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Запускаем горутины для каждого возможного хода
|
||||
for _, cell := range emptyCells {
|
||||
wg.Add(1)
|
||||
go func(r, c int) {
|
||||
defer wg.Done()
|
||||
boardCopy := p.copyBoard(board)
|
||||
boardCopy.Board[r][c] = p.Figure
|
||||
score := p.minimaxRecursive(boardCopy, 0, false, maxDepth)
|
||||
resultChan <- moveResult{move: []int{r, c}, score: score}
|
||||
}(cell[0], cell[1])
|
||||
}
|
||||
|
||||
wg.Wait() // Ждем завершения всех горутин
|
||||
close(resultChan) // Закрываем канал
|
||||
|
||||
// Определяем лучший ход
|
||||
for result := range resultChan {
|
||||
if result.score > bestScore {
|
||||
bestScore = result.score
|
||||
bestMove = result.move
|
||||
}
|
||||
}
|
||||
|
||||
if bestMove == nil {
|
||||
// Если по какой-то причине лучший ход не найден (маловероятно)
|
||||
// переходи на стратегию поведения среднего уровня сложности
|
||||
return p.makeMediumMove(board)
|
||||
}
|
||||
|
||||
return bestMove[0], bestMove[1]
|
||||
}
|
||||
|
||||
// Рекурсивная часть минимакса с ограничением глубины
|
||||
func (p *ComputerPlayer) minimaxRecursive(
|
||||
board *b.Board, depth int, isMaximizing bool,
|
||||
maxDepthLimit int,
|
||||
) int {
|
||||
opponentFigure := b.Cross
|
||||
if p.Figure == b.Cross {
|
||||
opponentFigure = b.Nought
|
||||
}
|
||||
|
||||
if board.CheckWin(p.Figure) {
|
||||
return 10 - depth // Выигрыш текущего игрока
|
||||
}
|
||||
if board.CheckWin(opponentFigure) {
|
||||
return depth - 10 // Проигрыш текущего игрока (выигрыш оппонента)
|
||||
}
|
||||
if board.CheckDraw() {
|
||||
return 0 // Ничья
|
||||
}
|
||||
|
||||
if depth >= maxDepthLimit { // Ограничение глубины
|
||||
// Если достигнута максимальная глубина, используем эвристическую оценку
|
||||
return p.evaluateBoardHeuristic(board, p.Figure)
|
||||
}
|
||||
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
|
||||
if isMaximizing {
|
||||
bestScore := -100000
|
||||
for _, cell := range emptyCells {
|
||||
boardCopy := p.copyBoard(board)
|
||||
boardCopy.Board[cell[0]][cell[1]] = p.Figure
|
||||
score := p.minimaxRecursive(
|
||||
boardCopy, depth+1, false, maxDepthLimit,
|
||||
)
|
||||
bestScore = max(bestScore, score)
|
||||
}
|
||||
return bestScore
|
||||
} else {
|
||||
bestScore := 100000
|
||||
// opponentFigure уже определен выше
|
||||
for _, cell := range emptyCells {
|
||||
boardCopy := p.copyBoard(board)
|
||||
boardCopy.Board[cell[0]][cell[1]] = opponentFigure
|
||||
score := p.minimaxRecursive(
|
||||
boardCopy, depth+1, true, maxDepthLimit,
|
||||
)
|
||||
bestScore = min(bestScore, score)
|
||||
}
|
||||
return bestScore
|
||||
}
|
||||
}
|
||||
121
part_7/tic_tac_toe_v6/player/strategy_zonebased.go
Normal file
121
part_7/tic_tac_toe_v6/player/strategy_zonebased.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
b "tic-tac-toe/board"
|
||||
)
|
||||
|
||||
// Параллельный анализ на основе зон
|
||||
func (p *ComputerPlayer) makeZoneBasedMove(board *b.Board) (int, int) {
|
||||
// Если доска не очень большая, используем эвристику
|
||||
if board.Size <= 5 { // Пороговое значение, можно настроить
|
||||
return p.makeParallelHeuristicMove(board)
|
||||
}
|
||||
|
||||
bestScore := -100000
|
||||
var bestMove []int
|
||||
emptyCells := p.getEmptyCells(board)
|
||||
if len(emptyCells) == 0 {
|
||||
return -1, -1
|
||||
}
|
||||
if len(emptyCells) == 1 {
|
||||
return emptyCells[0][0], emptyCells[0][1]
|
||||
}
|
||||
|
||||
// Определяем размер зоны (например, 3x3)
|
||||
zoneSize := 3
|
||||
if board.Size < zoneSize {
|
||||
zoneSize = board.Size // Если доска меньше зоны, зона равна доске
|
||||
}
|
||||
|
||||
type moveResult struct {
|
||||
move []int
|
||||
score int
|
||||
}
|
||||
// Используем буферизированный канал, чтобы не блокировать горутины,
|
||||
// если основная горутина не успевает обрабатывать результаты
|
||||
|
||||
// Размер канала равен количеству пустых клеток,
|
||||
// т.к. для каждой может быть запущена горутина
|
||||
resultChan := make(chan moveResult, len(emptyCells))
|
||||
numZonesToProcess := 0 // Счетчик для корректного ожидания
|
||||
|
||||
for _, cell := range emptyCells {
|
||||
numZonesToProcess++
|
||||
// Запускаем горутину для каждой пустой клетки
|
||||
go func(centerCell []int) {
|
||||
localBestScore := -100000
|
||||
var localBestMove []int
|
||||
|
||||
// Определяем границы зоны вокруг centerCell
|
||||
minRow := max(0, centerCell[0]-zoneSize/2)
|
||||
maxRow := min(board.Size-1, centerCell[0]+zoneSize/2)
|
||||
minCol := max(0, centerCell[1]-zoneSize/2)
|
||||
maxCol := min(board.Size-1, centerCell[1]+zoneSize/2)
|
||||
|
||||
// Ищем ходы в зоне
|
||||
foundMoveInZone := false
|
||||
for r := minRow; r <= maxRow; r++ {
|
||||
for c := minCol; c <= maxCol; c++ {
|
||||
// Если найден пустая клетка в зоне
|
||||
if board.Board[r][c] == b.Empty {
|
||||
foundMoveInZone = true
|
||||
boardCopy := p.copyBoard(board)
|
||||
boardCopy.Board[r][c] = p.Figure
|
||||
// Оцениваем ход испо
|
||||
score := p.evaluateBoardHeuristic(boardCopy, p.Figure)
|
||||
|
||||
// Если найден лучший ход
|
||||
if score > localBestScore {
|
||||
localBestScore = score
|
||||
localBestMove = []int{r, c}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если найден лучший ход в зоне
|
||||
if foundMoveInZone && localBestMove != nil {
|
||||
resultChan <- moveResult{
|
||||
move: localBestMove, score: localBestScore,
|
||||
}
|
||||
} else if !foundMoveInZone &&
|
||||
board.Board[centerCell[0]][centerCell[1]] == b.Empty {
|
||||
// Если зона вокруг centerCell не содержит других
|
||||
// пустых клеток, но сама centerCell пуста –
|
||||
// оцениваем ход в centerCell
|
||||
boardCopy := p.copyBoard(board)
|
||||
boardCopy.Board[centerCell[0]][centerCell[1]] = p.Figure
|
||||
score := p.evaluateBoardHeuristic(boardCopy, p.Figure)
|
||||
resultChan <- moveResult{move: centerCell, score: score}
|
||||
} else {
|
||||
// Если не найдено ходов в зоне или centerCell не пуста
|
||||
// (не должно случиться, если итерируем по emptyCells),
|
||||
// отправляем фиктивный результат,
|
||||
// чтобы не блокировать ожидание.
|
||||
// Этого не должно происходить в нормальном потоке.
|
||||
resultChan <- moveResult{move: nil, score: -200000}
|
||||
}
|
||||
}(cell)
|
||||
}
|
||||
|
||||
// Ожидаем завершения всех горутин
|
||||
processedGoroutines := 0 // Счетчик для корректного ожидания
|
||||
for processedGoroutines < numZonesToProcess {
|
||||
result := <-resultChan
|
||||
processedGoroutines++
|
||||
// Если найден лучший ход
|
||||
if result.move != nil && result.score > bestScore {
|
||||
bestScore = result.score
|
||||
bestMove = result.move
|
||||
}
|
||||
}
|
||||
|
||||
if bestMove == nil {
|
||||
// Если по какой-то причине лучший ход не найден (маловероятно)
|
||||
// переходи на стратегию поведения среднего уровня сложности
|
||||
return p.makeMediumMove(board)
|
||||
}
|
||||
|
||||
// Возвращаем лучший ход
|
||||
return bestMove[0], bestMove[1]
|
||||
}
|
||||
BIN
part_7/tic_tac_toe_v6/tic_tac_toe.db
Normal file
BIN
part_7/tic_tac_toe_v6/tic_tac_toe.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user