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.
@@ -0,0 +1,7 @@
1
+ include wincurl_android/icon.png
2
+ include wincurl_android/*.wav
3
+ prune wincurl_android/.buildozer
4
+ prune wincurl_android/.venv
5
+ prune wincurl_android/wincurl_build_clean
6
+ prune wincurl_android/wincurl_cache
7
+ prune wincurl_android/__pycache__
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ [console_scripts]
2
+ wincurl3 = wincurl3.main:main
@@ -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