Skip to content

Commit d66fe98

Browse files
committed
feat: add balatro.sh script for running multiple instances
1 parent 8092f26 commit d66fe98

File tree

2 files changed

+343
-1
lines changed

2 files changed

+343
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ runs/*.jsonl
66
src/lua_old
77
balatrobot_old.lua
88
scripts
9-
balatro.sh
109
dump
1110
coverage.xml
1211
.coverage

balatro.sh

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
#!/bin/bash
2+
3+
# Global variables
4+
declare -a PORTS=()
5+
declare -a INSTANCE_PIDS=()
6+
declare -a FAILED_PORTS=()
7+
HEADLESS=false
8+
FAST=false
9+
FORCE_KILL=true
10+
KILL_ONLY=false
11+
12+
# Platform detection
13+
case "$OSTYPE" in
14+
darwin*)
15+
PLATFORM="macos"
16+
;;
17+
linux-gnu*)
18+
PLATFORM="linux"
19+
;;
20+
*)
21+
echo "Error: Unsupported platform: $OSTYPE" >&2
22+
echo "Supported platforms: macOS, Linux" >&2
23+
exit 1
24+
;;
25+
esac
26+
27+
# Usage function
28+
show_usage() {
29+
cat <<EOF
30+
Usage: $0 -p PORT [OPTIONS]
31+
$0 --kill
32+
33+
Required (unless --kill):
34+
-p, --port PORT Specify port for Balatro instance (can be used multiple times)
35+
36+
Options:
37+
--headless Enable headless mode (sets BALATROBOT_HEADLESS=1)
38+
--fast Enable fast mode (sets BALATROBOT_FAST=1)
39+
--kill Kill all running Balatro instances and exit
40+
-h, --help Show this help message
41+
42+
Examples:
43+
$0 -p 12347 # Start single instance on port 12347
44+
$0 -p 12346 -p 12347 # Start two instances on ports 12346 and 12347
45+
$0 --headless --fast -p 12346 # Start with headless and fast mode
46+
$0 --kill # Kill all running Balatro instances
47+
48+
EOF
49+
}
50+
51+
# Parse command line arguments
52+
parse_arguments() {
53+
while [[ $# -gt 0 ]]; do
54+
case $1 in
55+
-p | --port)
56+
if [[ -z "$2" ]] || [[ "$2" =~ ^- ]]; then
57+
echo "Error: --port requires a port number" >&2
58+
exit 1
59+
fi
60+
if ! [[ "$2" =~ ^[0-9]+$ ]] || [[ "$2" -lt 1024 ]] || [[ "$2" -gt 65535 ]]; then
61+
echo "Error: Port must be a number between 1024 and 65535" >&2
62+
exit 1
63+
fi
64+
PORTS+=("$2")
65+
shift 2
66+
;;
67+
--headless)
68+
HEADLESS=true
69+
shift
70+
;;
71+
--fast)
72+
FAST=true
73+
shift
74+
;;
75+
--kill)
76+
KILL_ONLY=true
77+
shift
78+
;;
79+
-h | --help)
80+
show_usage
81+
exit 0
82+
;;
83+
*)
84+
echo "Error: Unknown option $1" >&2
85+
show_usage
86+
exit 1
87+
;;
88+
esac
89+
done
90+
91+
# Validate arguments based on mode
92+
if [[ "$KILL_ONLY" == "true" ]]; then
93+
# In kill mode, no ports are required
94+
if [[ ${#PORTS[@]} -gt 0 ]]; then
95+
echo "Error: --kill cannot be used with port specifications" >&2
96+
show_usage
97+
exit 1
98+
fi
99+
else
100+
# In normal mode, at least one port must be specified
101+
if [[ ${#PORTS[@]} -eq 0 ]]; then
102+
echo "Error: At least one port must be specified with -p/--port" >&2
103+
show_usage
104+
exit 1
105+
fi
106+
fi
107+
108+
# Remove duplicates from ports array
109+
local unique_ports=()
110+
for port in "${PORTS[@]}"; do
111+
if [[ ! " ${unique_ports[*]} " =~ " ${port} " ]]; then
112+
unique_ports+=("$port")
113+
fi
114+
done
115+
PORTS=("${unique_ports[@]}")
116+
}
117+
118+
# Check if a port is available
119+
check_port_availability() {
120+
local port=$1
121+
122+
# Check if port is in use
123+
if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null 2>&1; then
124+
if [[ "$FORCE_KILL" == "true" ]]; then
125+
lsof -ti:"$port" | xargs kill -9 2>/dev/null
126+
sleep 1
127+
128+
# Verify port is now free
129+
if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null 2>&1; then
130+
echo "Error: Could not free port $port" >&2
131+
return 1
132+
fi
133+
else
134+
return 1
135+
fi
136+
fi
137+
return 0
138+
}
139+
140+
# Get platform-specific configuration
141+
get_platform_config() {
142+
case "$PLATFORM" in
143+
macos)
144+
# macOS Steam path and configuration
145+
STEAM_PATH="/Users/$USER/Library/Application Support/Steam/steamapps/common/Balatro"
146+
LIBRARY_ENV_VAR="DYLD_INSERT_LIBRARIES"
147+
LIBRARY_FILE="liblovely.dylib"
148+
BALATRO_EXECUTABLE="Balatro.app/Contents/MacOS/love"
149+
PROCESS_PATTERNS=("Balatro\.app" "balatro\.sh")
150+
;;
151+
linux)
152+
# TODO: Implement Linux paths
153+
# Common Linux Steam paths to check:
154+
# - ~/.steam/steam/steamapps/common/Balatro
155+
# - ~/.local/share/Steam/steamapps/common/Balatro
156+
# - Custom detection logic
157+
echo "Error: Linux support is not yet implemented" >&2
158+
echo "TODO: Add Linux Steam path detection and configuration" >&2
159+
exit 1
160+
# STEAM_PATH="TODO: Implement Linux Steam path detection"
161+
# LIBRARY_ENV_VAR="LD_PRELOAD"
162+
# LIBRARY_FILE="liblovely.so"
163+
# BALATRO_EXECUTABLE="Balatro"
164+
# TODO: Verify actual Linux process names for process killing
165+
# PROCESS_PATTERNS=("Balatro" "balatro\.sh")
166+
;;
167+
*)
168+
echo "Error: Unsupported platform configuration" >&2
169+
exit 1
170+
;;
171+
esac
172+
}
173+
174+
# Create logs directory
175+
create_logs_directory() {
176+
if [[ ! -d "logs" ]]; then
177+
mkdir -p logs
178+
if [[ $? -ne 0 ]]; then
179+
echo "Error: Could not create logs directory" >&2
180+
return 1
181+
fi
182+
fi
183+
return 0
184+
}
185+
186+
# Kill existing Balatro processes
187+
kill_existing_processes() {
188+
# Build platform-specific grep pattern
189+
local grep_pattern=""
190+
for i in "${!PROCESS_PATTERNS[@]}"; do
191+
if [[ $i -eq 0 ]]; then
192+
grep_pattern="${PROCESS_PATTERNS[$i]}"
193+
else
194+
grep_pattern="$grep_pattern|${PROCESS_PATTERNS[$i]}"
195+
fi
196+
done
197+
198+
if ps aux | grep -E "($grep_pattern)" | grep -v grep >/dev/null; then
199+
# Kill processes using platform-specific patterns
200+
for pattern in "${PROCESS_PATTERNS[@]}"; do
201+
pkill -f "$pattern" 2>/dev/null
202+
done
203+
sleep 2
204+
205+
# Force kill if still running
206+
if ps aux | grep -E "($grep_pattern)" | grep -v grep >/dev/null; then
207+
for pattern in "${PROCESS_PATTERNS[@]}"; do
208+
pkill -9 -f "$pattern" 2>/dev/null
209+
done
210+
sleep 1
211+
fi
212+
fi
213+
}
214+
215+
# Start a single Balatro instance
216+
start_balatro_instance() {
217+
local port=$1
218+
local log_file="logs/balatro_${port}.log"
219+
220+
# Remove old log file for this port
221+
if [[ -f "$log_file" ]]; then
222+
rm "$log_file"
223+
fi
224+
225+
# Set environment variables
226+
export BALATROBOT_PORT="$port"
227+
if [[ "$HEADLESS" == "true" ]]; then
228+
export BALATROBOT_HEADLESS=1
229+
fi
230+
if [[ "$FAST" == "true" ]]; then
231+
export BALATROBOT_FAST=1
232+
fi
233+
234+
# Set up platform-specific Balatro configuration
235+
export ${LIBRARY_ENV_VAR}="${STEAM_PATH}/${LIBRARY_FILE}"
236+
237+
# Start the process
238+
"${STEAM_PATH}/${BALATRO_EXECUTABLE}" >"$log_file" 2>&1 &
239+
local pid=$!
240+
241+
# Verify process started
242+
sleep 2
243+
if ! ps -p $pid >/dev/null; then
244+
echo "ERROR: Balatro instance failed to start on port $port. Check $log_file for details." >&2
245+
FAILED_PORTS+=("$port")
246+
return 1
247+
fi
248+
249+
INSTANCE_PIDS+=("$pid")
250+
return 0
251+
}
252+
253+
# Print information about running instances
254+
print_instance_info() {
255+
local success_count=0
256+
257+
for i in "${!PORTS[@]}"; do
258+
local port=${PORTS[$i]}
259+
local log_file="logs/balatro_${port}.log"
260+
261+
if [[ " ${FAILED_PORTS[*]} " =~ " ${port} " ]]; then
262+
echo "• Port $port, FAILED, Log: $log_file"
263+
else
264+
local pid=${INSTANCE_PIDS[$success_count]}
265+
echo "• Port $port, PID $pid, Log: $log_file"
266+
((success_count++))
267+
fi
268+
done
269+
}
270+
271+
# Cleanup function for signal handling
272+
cleanup() {
273+
echo ""
274+
echo "Script interrupted. Cleaning up..."
275+
if [[ ${#INSTANCE_PIDS[@]} -gt 0 ]]; then
276+
echo "Killing running Balatro instances..."
277+
for pid in "${INSTANCE_PIDS[@]}"; do
278+
kill "$pid" 2>/dev/null
279+
done
280+
fi
281+
exit 1
282+
}
283+
284+
# Trap signals for cleanup
285+
trap cleanup SIGINT SIGTERM
286+
287+
# Main execution
288+
main() {
289+
# Get platform configuration
290+
get_platform_config
291+
292+
# Parse arguments
293+
parse_arguments "$@"
294+
295+
# Handle kill-only mode
296+
if [[ "$KILL_ONLY" == "true" ]]; then
297+
echo "Killing all running Balatro instances..."
298+
kill_existing_processes
299+
echo "All Balatro instances have been terminated."
300+
exit 0
301+
fi
302+
303+
# Create logs directory
304+
if ! create_logs_directory; then
305+
exit 1
306+
fi
307+
308+
# Kill existing processes
309+
kill_existing_processes
310+
311+
# Check port availability and start instances
312+
local failed_count=0
313+
for port in "${PORTS[@]}"; do
314+
if ! check_port_availability "$port"; then
315+
echo "Error: Port $port is not available" >&2
316+
FAILED_PORTS+=("$port")
317+
((failed_count++))
318+
continue
319+
fi
320+
321+
if ! start_balatro_instance "$port"; then
322+
((failed_count++))
323+
continue
324+
fi
325+
326+
done
327+
328+
# Print final status
329+
print_instance_info
330+
331+
# Determine exit code
332+
local success_count=$((${#PORTS[@]} - failed_count))
333+
if [[ $failed_count -eq 0 ]]; then
334+
exit 0
335+
elif [[ $success_count -eq 0 ]]; then
336+
exit 3
337+
else
338+
exit 4
339+
fi
340+
}
341+
342+
# Run main function with all arguments
343+
main "$@"

0 commit comments

Comments
 (0)