wincurl3 19.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wincurl3-19.3.0/MANIFEST.in +7 -0
- wincurl3-19.3.0/PKG-INFO +11 -0
- wincurl3-19.3.0/setup.cfg +4 -0
- wincurl3-19.3.0/setup.py +20 -0
- wincurl3-19.3.0/wincurl3.egg-info/PKG-INFO +11 -0
- wincurl3-19.3.0/wincurl3.egg-info/SOURCES.txt +21 -0
- wincurl3-19.3.0/wincurl3.egg-info/dependency_links.txt +1 -0
- wincurl3-19.3.0/wincurl3.egg-info/entry_points.txt +2 -0
- wincurl3-19.3.0/wincurl3.egg-info/requires.txt +1 -0
- wincurl3-19.3.0/wincurl3.egg-info/top_level.txt +1 -0
- wincurl3-19.3.0/wincurl_android/__init__.py +0 -0
- wincurl3-19.3.0/wincurl_android/bootstrap.py +16 -0
- wincurl3-19.3.0/wincurl_android/bot_script.py +254 -0
- wincurl3-19.3.0/wincurl_android/icon.png +0 -0
- wincurl3-19.3.0/wincurl_android/main.py +2568 -0
- wincurl3-19.3.0/wincurl_android/test.wav +0 -0
- wincurl3-19.3.0/wincurl_android/test_ai.py +119 -0
- wincurl3-19.3.0/wincurl_android/test_chatfont.py +91 -0
- wincurl3-19.3.0/wincurl_android/test_font.py +11 -0
- wincurl3-19.3.0/wincurl_android/test_font2.py +15 -0
- wincurl3-19.3.0/wincurl_android/test_font3.py +16 -0
- wincurl3-19.3.0/wincurl_android/test_metrics.py +5 -0
- wincurl3-19.3.0/wincurl_android/theme.wav +0 -0
wincurl3-19.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wincurl3
|
|
3
|
+
Version: 19.3.0
|
|
4
|
+
Summary: WinCurl 3D - A Multiplayer Curling Simulator
|
|
5
|
+
Author: Jason
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: pygame-ce>=2.5.0
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: requires-dist
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
Dynamic: summary
|
wincurl3-19.3.0/setup.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='wincurl3',
|
|
5
|
+
version='19.3.0',
|
|
6
|
+
description='WinCurl 3D - A Multiplayer Curling Simulator',
|
|
7
|
+
author='Jason',
|
|
8
|
+
packages=['wincurl3'],
|
|
9
|
+
package_dir={'wincurl3': 'wincurl_android'},
|
|
10
|
+
include_package_data=True,
|
|
11
|
+
install_requires=[
|
|
12
|
+
'pygame-ce>=2.5.0',
|
|
13
|
+
],
|
|
14
|
+
entry_points={
|
|
15
|
+
'console_scripts': [
|
|
16
|
+
'wincurl3=wincurl3.main:main',
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
python_requires='>=3.8',
|
|
20
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wincurl3
|
|
3
|
+
Version: 19.3.0
|
|
4
|
+
Summary: WinCurl 3D - A Multiplayer Curling Simulator
|
|
5
|
+
Author: Jason
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: pygame-ce>=2.5.0
|
|
8
|
+
Dynamic: author
|
|
9
|
+
Dynamic: requires-dist
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
Dynamic: summary
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
setup.py
|
|
3
|
+
wincurl3.egg-info/PKG-INFO
|
|
4
|
+
wincurl3.egg-info/SOURCES.txt
|
|
5
|
+
wincurl3.egg-info/dependency_links.txt
|
|
6
|
+
wincurl3.egg-info/entry_points.txt
|
|
7
|
+
wincurl3.egg-info/requires.txt
|
|
8
|
+
wincurl3.egg-info/top_level.txt
|
|
9
|
+
wincurl_android/__init__.py
|
|
10
|
+
wincurl_android/bootstrap.py
|
|
11
|
+
wincurl_android/bot_script.py
|
|
12
|
+
wincurl_android/icon.png
|
|
13
|
+
wincurl_android/main.py
|
|
14
|
+
wincurl_android/test.wav
|
|
15
|
+
wincurl_android/test_ai.py
|
|
16
|
+
wincurl_android/test_chatfont.py
|
|
17
|
+
wincurl_android/test_font.py
|
|
18
|
+
wincurl_android/test_font2.py
|
|
19
|
+
wincurl_android/test_font3.py
|
|
20
|
+
wincurl_android/test_metrics.py
|
|
21
|
+
wincurl_android/theme.wav
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pygame-ce>=2.5.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wincurl3
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# bootstrap.py
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Minimal flag
|
|
6
|
+
IS_ANDROID = hasattr(sys, 'getandroidapilevel') or 'ANDROID_ARGUMENT' in os.environ
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
print(f"DEBUG: IS_ANDROID is {IS_ANDROID}")
|
|
10
|
+
# Minimal pygame init
|
|
11
|
+
import pygame
|
|
12
|
+
pygame.init()
|
|
13
|
+
print("Pygame initialized successfully.")
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import os
|
|
2
|
+
os.environ["SDL_VIDEODRIVER"] = "dummy"
|
|
3
|
+
|
|
4
|
+
import pygame
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
# Import main directly
|
|
13
|
+
import main as wc
|
|
14
|
+
|
|
15
|
+
class BotCurl(wc.WinCurl3):
|
|
16
|
+
def __init__(self, room_name):
|
|
17
|
+
super().__init__()
|
|
18
|
+
try: pygame.mixer.music.stop()
|
|
19
|
+
except: pass
|
|
20
|
+
self.setup_display()
|
|
21
|
+
self.room_name = room_name
|
|
22
|
+
self.app_state = "MENU"
|
|
23
|
+
self.game_mode = "JOIN"
|
|
24
|
+
self.net.connect("Bot", False, self.room_name, 0)
|
|
25
|
+
self.bot_timer = 0
|
|
26
|
+
|
|
27
|
+
self.chat_queue = []
|
|
28
|
+
self.last_chat_len = 0
|
|
29
|
+
def file_reader():
|
|
30
|
+
import os, time
|
|
31
|
+
with open("chat_out.txt", "w") as f: f.write("")
|
|
32
|
+
while True:
|
|
33
|
+
if os.path.exists("chat_in.txt"):
|
|
34
|
+
with open("chat_in.txt", "r") as f:
|
|
35
|
+
content = f.read().strip()
|
|
36
|
+
if content:
|
|
37
|
+
for line in content.split("\n"):
|
|
38
|
+
if line.strip(): self.chat_queue.append(line.strip())
|
|
39
|
+
open("chat_in.txt", "w").close()
|
|
40
|
+
time.sleep(0.5)
|
|
41
|
+
threading.Thread(target=file_reader, daemon=True).start()
|
|
42
|
+
|
|
43
|
+
def calculate_best_shot(self):
|
|
44
|
+
import math
|
|
45
|
+
FRICTION_BASE = 0.022
|
|
46
|
+
|
|
47
|
+
class SimStone:
|
|
48
|
+
def __init__(self, p, v, c, team):
|
|
49
|
+
self.pos = pygame.math.Vector2(p.x, p.y)
|
|
50
|
+
self.vel = pygame.math.Vector2(v.x, v.y)
|
|
51
|
+
self.curl = c
|
|
52
|
+
self.team = team
|
|
53
|
+
self.is_moving = (self.vel.length() > 0)
|
|
54
|
+
|
|
55
|
+
def update(self):
|
|
56
|
+
if not self.is_moving: return
|
|
57
|
+
speed = self.vel.length()
|
|
58
|
+
current_friction = max(0.008, FRICTION_BASE)
|
|
59
|
+
if speed <= current_friction:
|
|
60
|
+
self.vel.update(0, 0)
|
|
61
|
+
self.is_moving = False
|
|
62
|
+
else:
|
|
63
|
+
self.vel.scale_to_length(speed - current_friction)
|
|
64
|
+
if speed > 0.4:
|
|
65
|
+
self.vel.rotate_ip((1.4 / speed) * self.curl * 0.05)
|
|
66
|
+
self.pos += self.vel
|
|
67
|
+
|
|
68
|
+
def simulate(vx, vy, curl):
|
|
69
|
+
sim_stones = []
|
|
70
|
+
for s in self.stones:
|
|
71
|
+
if s != self.active_stone:
|
|
72
|
+
sim_stones.append(SimStone(s.pos, pygame.math.Vector2(0,0), 0, s.team))
|
|
73
|
+
sim_active = SimStone(self.hack_pos, pygame.math.Vector2(vx, vy), curl, self.current_team)
|
|
74
|
+
sim_stones.append(sim_active)
|
|
75
|
+
|
|
76
|
+
steps = 0
|
|
77
|
+
while any(s.is_moving for s in sim_stones) and steps < 3000:
|
|
78
|
+
steps += 1
|
|
79
|
+
for s in sim_stones: s.update()
|
|
80
|
+
|
|
81
|
+
for i in range(len(sim_stones)):
|
|
82
|
+
s1 = sim_stones[i]
|
|
83
|
+
for j in range(i+1, len(sim_stones)):
|
|
84
|
+
s2 = sim_stones[j]
|
|
85
|
+
diff = s2.pos - s1.pos
|
|
86
|
+
dist = diff.length()
|
|
87
|
+
if dist < 64:
|
|
88
|
+
if dist == 0: dist = 0.001
|
|
89
|
+
overlap = 64 - dist
|
|
90
|
+
normal = diff / dist
|
|
91
|
+
s1.pos -= normal * (overlap/2)
|
|
92
|
+
s2.pos += normal * (overlap/2)
|
|
93
|
+
rel_vel = s2.vel - s1.vel
|
|
94
|
+
speed = rel_vel.dot(normal)
|
|
95
|
+
if speed < 0:
|
|
96
|
+
impulse = -(1 + 0.95) * speed / 2
|
|
97
|
+
s1.vel -= normal * impulse
|
|
98
|
+
s2.vel += normal * impulse
|
|
99
|
+
s1.is_moving = True
|
|
100
|
+
s2.is_moving = True
|
|
101
|
+
|
|
102
|
+
score = 0
|
|
103
|
+
for s in sim_stones:
|
|
104
|
+
dist_to_button = (s.pos - self.house_pos).length()
|
|
105
|
+
if dist_to_button < 160:
|
|
106
|
+
pts = (160 - dist_to_button)
|
|
107
|
+
if s.team == self.current_team:
|
|
108
|
+
score += pts
|
|
109
|
+
else:
|
|
110
|
+
score -= pts * 1.5
|
|
111
|
+
elif s.team == self.current_team and s.pos.y > self.house_pos.y + 160 and s.pos.y < self.house_pos.y + 400:
|
|
112
|
+
if abs(s.pos.x - self.house_pos.x) < 64:
|
|
113
|
+
score += 30
|
|
114
|
+
return score
|
|
115
|
+
|
|
116
|
+
best_score = -999999
|
|
117
|
+
best_shot = None
|
|
118
|
+
|
|
119
|
+
opponent_stones = [s for s in self.stones if s.team != self.current_team and s != self.active_stone and (s.pos - self.house_pos).length() < 200]
|
|
120
|
+
targets = [(os.pos, True) for os in opponent_stones]
|
|
121
|
+
targets.extend([
|
|
122
|
+
(self.house_pos, False),
|
|
123
|
+
(self.house_pos + pygame.math.Vector2(0, 200), False),
|
|
124
|
+
(self.house_pos + pygame.math.Vector2(-40, 0), False),
|
|
125
|
+
(self.house_pos + pygame.math.Vector2(40, 0), False)
|
|
126
|
+
])
|
|
127
|
+
|
|
128
|
+
for target, is_takeout in targets:
|
|
129
|
+
dist = (target - self.hack_pos).length()
|
|
130
|
+
base_speeds = [math.sqrt(2 * FRICTION_BASE * (dist + 400))] if is_takeout else [math.sqrt(2 * FRICTION_BASE * dist) + 0.05]
|
|
131
|
+
|
|
132
|
+
for speed in base_speeds:
|
|
133
|
+
for curl in [-1.0, 0.0, 1.0]:
|
|
134
|
+
base_dir = (target - self.hack_pos).normalize()
|
|
135
|
+
base_angle = math.atan2(base_dir.y, base_dir.x)
|
|
136
|
+
for angle_offset in [-0.04, -0.02, 0.0, 0.02, 0.04]:
|
|
137
|
+
test_angle = base_angle + angle_offset + (curl * 0.015)
|
|
138
|
+
vx = math.cos(test_angle) * speed
|
|
139
|
+
vy = math.sin(test_angle) * speed
|
|
140
|
+
score = simulate(vx, vy, curl)
|
|
141
|
+
if score > best_score:
|
|
142
|
+
best_score = score
|
|
143
|
+
best_shot = (vx, vy, curl)
|
|
144
|
+
|
|
145
|
+
if not best_shot:
|
|
146
|
+
speed = math.sqrt(2 * FRICTION_BASE * (self.house_pos - self.hack_pos).length())
|
|
147
|
+
dir = (self.house_pos - self.hack_pos).normalize()
|
|
148
|
+
best_shot = (dir.x * speed, dir.y * speed, 0.0)
|
|
149
|
+
|
|
150
|
+
import random
|
|
151
|
+
fx, fy, fc = best_shot
|
|
152
|
+
speed = math.hypot(fx, fy)
|
|
153
|
+
angle = math.atan2(fy, fx)
|
|
154
|
+
speed += random.uniform(-0.02, 0.02)
|
|
155
|
+
angle += random.uniform(-0.002, 0.002)
|
|
156
|
+
return math.cos(angle) * speed, math.sin(angle) * speed, fc
|
|
157
|
+
def run(self):
|
|
158
|
+
while True:
|
|
159
|
+
self.frames_elapsed += 1
|
|
160
|
+
for event in pygame.event.get():
|
|
161
|
+
if event.type == pygame.QUIT:
|
|
162
|
+
self.net.close()
|
|
163
|
+
pygame.quit()
|
|
164
|
+
sys.exit()
|
|
165
|
+
|
|
166
|
+
if self.chat_queue:
|
|
167
|
+
msg = self.chat_queue.pop(0)
|
|
168
|
+
self.net.send_action({'cmd': 'chat', 'msg': msg})
|
|
169
|
+
self.chat_messages.append({'text': f"Bot: {msg}", 'time': pygame.time.get_ticks()})
|
|
170
|
+
self.last_chat_len = len(self.chat_messages)
|
|
171
|
+
|
|
172
|
+
self.update_network()
|
|
173
|
+
|
|
174
|
+
while self.last_chat_len < len(self.chat_messages):
|
|
175
|
+
c = self.chat_messages[self.last_chat_len]['text']
|
|
176
|
+
if not c.startswith("Bot:"):
|
|
177
|
+
print(f"CHAT_IN: {c}", flush=True)
|
|
178
|
+
with open("chat_out.txt", "a") as f: f.write(c + "\n")
|
|
179
|
+
self.last_chat_len += 1
|
|
180
|
+
if self.frames_elapsed % 60 == 0:
|
|
181
|
+
print(f"Frame: {self.frames_elapsed}, Room: {self.room_name}, State: {self.app_state}, Turn: {getattr(self, 'turn_state', 'NONE')}, Team: {getattr(self, 'current_team', -1)}, Color: {getattr(self, 'preferred_color', -1)}, NetRunning: {self.net.running}, Matched: {self.net.matched}", flush=True)
|
|
182
|
+
|
|
183
|
+
if not self.net.running and not self.net.matched and self.frames_elapsed % 300 == 0:
|
|
184
|
+
print("Network disconnected or failed. Reconnecting...", flush=True)
|
|
185
|
+
self.net.close()
|
|
186
|
+
self.net = wc.WinCurlNet()
|
|
187
|
+
self.net.connect("Bot", False, self.room_name, 0)
|
|
188
|
+
|
|
189
|
+
if self.app_state == "MATCH_OVER":
|
|
190
|
+
if self.bot_timer == 0:
|
|
191
|
+
self.bot_timer = pygame.time.get_ticks()
|
|
192
|
+
elif pygame.time.get_ticks() - self.bot_timer > 5000:
|
|
193
|
+
print("Match over. Returning to menu to wait for next match.", flush=True)
|
|
194
|
+
self.net.close()
|
|
195
|
+
self.net = wc.IRCNetworkManager()
|
|
196
|
+
self.app_state = "MENU"
|
|
197
|
+
self.game_mode = "JOIN"
|
|
198
|
+
self.net.connect("Bot", False, self.room_name, 0)
|
|
199
|
+
self.bot_timer = 0
|
|
200
|
+
|
|
201
|
+
if self.app_state == "COIN_TOSS":
|
|
202
|
+
if self.game_mode == "JOIN" and getattr(self, 'coin_flip_result', -1) == -1:
|
|
203
|
+
pass
|
|
204
|
+
else:
|
|
205
|
+
self.coin_timer -= 1
|
|
206
|
+
if self.coin_timer <= 0:
|
|
207
|
+
self.stones_thrown = {0: 0, 1: 0}
|
|
208
|
+
self.score = {0: [0]*8, 1: [0]*8}
|
|
209
|
+
self.current_end = 1
|
|
210
|
+
self.total_stones_played = 0
|
|
211
|
+
self.hammer_team = getattr(self, 'coin_flip_result', 0)
|
|
212
|
+
self.app_state = "PLAY"; self.reset_end()
|
|
213
|
+
|
|
214
|
+
if self.app_state == "PLAY":
|
|
215
|
+
if getattr(self, 'turn_state', 'NONE') == "AIMING" and self.current_team == getattr(self, 'preferred_color', 0):
|
|
216
|
+
if self.bot_timer == 0:
|
|
217
|
+
self.bot_timer = pygame.time.get_ticks()
|
|
218
|
+
elif pygame.time.get_ticks() - self.bot_timer > 2000:
|
|
219
|
+
vx, vy, curl = self.calculate_best_shot()
|
|
220
|
+
svel = pygame.math.Vector2(vx, vy)
|
|
221
|
+
self.selected_curl = curl
|
|
222
|
+
self.active_stone.vel = svel
|
|
223
|
+
self.active_stone.curl = self.selected_curl
|
|
224
|
+
self.active_stone.is_moving = True
|
|
225
|
+
|
|
226
|
+
self.net.send_action({'cmd': 'shoot', 'vx': svel.x, 'vy': svel.y, 'c': self.selected_curl})
|
|
227
|
+
|
|
228
|
+
self.stones_thrown[self.current_team] += 1
|
|
229
|
+
self.total_stones_played += 1
|
|
230
|
+
self.turn_state = "SLIDING"
|
|
231
|
+
self.curler_anim.update("LUNGING")
|
|
232
|
+
try:
|
|
233
|
+
self.audio.play_throw()
|
|
234
|
+
except:
|
|
235
|
+
pass
|
|
236
|
+
self.bot_timer = 0
|
|
237
|
+
|
|
238
|
+
elif getattr(self, 'turn_state', 'NONE') == "SLIDING":
|
|
239
|
+
self.update_physics()
|
|
240
|
+
elif getattr(self, 'turn_state', 'NONE') == "END":
|
|
241
|
+
if self.bot_timer == 0:
|
|
242
|
+
self.bot_timer = pygame.time.get_ticks()
|
|
243
|
+
elif pygame.time.get_ticks() - self.bot_timer > 3000:
|
|
244
|
+
self.advance_end_logic()
|
|
245
|
+
self.bot_timer = 0
|
|
246
|
+
|
|
247
|
+
self.clock.tick(60)
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
room_name = "WinCurl"
|
|
251
|
+
if len(sys.argv) > 1:
|
|
252
|
+
room_name = sys.argv[1]
|
|
253
|
+
bot = BotCurl(room_name)
|
|
254
|
+
bot.run()
|
|
Binary file
|