-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathai.go
More file actions
125 lines (100 loc) · 2.84 KB
/
ai.go
File metadata and controls
125 lines (100 loc) · 2.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package main
import (
"cmp"
"slices"
rl "github.com/gen2brain/raylib-go/raylib"
)
type searchPair struct {
actor, target *Stone
score float32
}
func compareSearchPairs(p1, p2 searchPair) int {
return cmp.Compare(p2.score, p1.score)
}
// / sufficiently "smart" AI
// / searches the options based on:
// / - proximity
// / - life state of the target stone
// / - life state of the hitting stone
// / - whether own stone will be hit in the process
// / - whether stone will richochet
func cpuSearchBestOption(level *Level, window *Window) (*Stone, *Stone) {
me := level.playerTurn
searchPairs := []searchPair{}
for i := range level.stones {
actor := &level.stones[i]
if actor.isDead || actor.playerId != me {
continue
}
for j := range level.stones {
if i == j {
continue
}
target := &level.stones[j]
if target.isDead || target.playerId == me {
continue
}
searchPairs = append(searchPairs, searchPair{
actor: actor,
target: target,
})
}
}
screenDiagonalSize := window.GetScreenDiagonal()
for pi := range searchPairs {
pair := &(searchPairs[pi])
actor, target := pair.actor, pair.target
ssOrigin := rl.Vector2Subtract(actor.pos, target.pos)
angle := 2 * rl.Vector2LineAngle(rl.Vector2Normalize(ssOrigin), rl.NewVector2(1, 0))
aTop := rl.Vector2Add(rl.Vector2Rotate(rl.NewVector2(0, -actor.radius), -angle), actor.pos)
aBottom := rl.Vector2Add(rl.Vector2Rotate(rl.NewVector2(0, actor.radius), -angle), actor.pos)
tTop := rl.Vector2Add(rl.Vector2Rotate(rl.NewVector2(0, -target.radius), -angle), target.pos)
tBottom := rl.Vector2Add(rl.Vector2Rotate(rl.NewVector2(0, target.radius), -angle), target.pos)
hitsOwn := false
richochets := false
for i := range level.stones {
stone := &level.stones[i]
if stone.isDead || (stone == actor || stone == target) {
continue
}
// line 1 check aTop - tTop
if rl.CheckCollisionCircleLine(stone.pos, stone.radius, aTop, tTop) {
hitsOwn = stone.playerId == me
richochets = true
}
// line 2 check aBottom - tBottom
if rl.CheckCollisionCircleLine(stone.pos, stone.radius, aBottom, tBottom) {
hitsOwn = stone.playerId == me
richochets = true
}
// line 3 check center to center
if rl.CheckCollisionCircleLine(stone.pos, stone.radius, actor.pos, target.pos) {
hitsOwn = stone.playerId == me
richochets = true
}
if hitsOwn && richochets {
break
}
}
distance := rl.Vector2Distance(actor.pos, target.pos) / screenDiagonalSize
pair.score -= distance
if hitsOwn {
pair.score += -1
}
if richochets {
pair.score += -0.5
}
if actor.life <= 5 {
pair.score += -0.5
}
if target.life <= 10 {
pair.score += 1
}
}
slices.SortFunc(searchPairs, compareSearchPairs)
if len(searchPairs) > 0 {
pair := searchPairs[0]
return pair.actor, pair.target
}
return nil, nil
}