brawl 0.0.0.alpha → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rspec +0 -0
- data/README.md +43 -0
- data/_design/ClassDescriptions.txt +98 -0
- data/_design/Psudo_Program.rb +113 -0
- data/_design/array.rb +4 -0
- data/_design/threads.rb +71 -0
- data/_spike/Communications/communications.rb +2 -0
- data/_spike/Communications/lib/communications/com_linker.rb +11 -0
- data/_spike/Communications/lib/communications/null_modem.rb +11 -0
- data/_spike/Communications/spec/communications/com_linker_spec.rb +37 -0
- data/_spike/DRb/arena.rb +32 -0
- data/_spike/DRb/battle_controller.rb +54 -0
- data/_spike/DRb/bot.rb +23 -0
- data/_spike/DRb/bot_runner.rb +110 -0
- data/_spike/DRb/comment.txt +11 -0
- data/_spike/DRb/server.rb +28 -0
- data/_spike/DRb/test_bot.rb +6 -0
- data/_spike/array.rb +7 -0
- data/_spike/atan2.rb +39 -0
- data/_spike/battle.rb +202 -0
- data/_spike/break.rb +10 -0
- data/_spike/circles.rb +29 -0
- data/_spike/cleaner_attribs.rb +13 -0
- data/_spike/comlink.rb +61 -0
- data/_spike/concat.rb +27 -0
- data/_spike/example_bot.rb +42 -0
- data/_spike/forwarding_instance.rb +32 -0
- data/_spike/hash_loop.rb +17 -0
- data/_spike/hashing.rb +7 -0
- data/_spike/hook.rb +19 -0
- data/_spike/mod.rb +10 -0
- data/_spike/point_in_cone.rb +20 -0
- data/_spike/runner.rb +44 -0
- data/_spike/safe.rb +28 -0
- data/brawl.gemspec +4 -3
- data/example/example_battle.rb +173 -0
- data/example/logview.txt +6394 -0
- data/lib/brawl.rb +14 -3
- data/lib/brawl/_helper.rb +65 -0
- data/lib/brawl/arena.rb +118 -0
- data/lib/brawl/basic_arena_object.rb +35 -0
- data/lib/brawl/basic_bot.rb +69 -0
- data/lib/brawl/battle_controller.rb +97 -0
- data/lib/brawl/bot_proxy.rb +60 -0
- data/lib/brawl/clock.rb +36 -0
- data/lib/brawl/parts.rb +3 -0
- data/lib/brawl/parts/basic_motor.rb +55 -0
- data/lib/brawl/parts/basic_scanner.rb +33 -0
- data/lib/brawl/parts/basic_weapon.rb +47 -0
- data/lib/brawl/version.rb +1 -1
- data/lib/brawl/wall.rb +9 -0
- data/spec/brawl/arena_spec.rb +163 -0
- data/spec/brawl/basic_arena_object_spec.rb +40 -0
- data/spec/brawl/basic_bot_spec.rb +84 -0
- data/spec/brawl/battle_controller_spec.rb +138 -0
- data/spec/brawl/clock_spec.rb +28 -0
- data/spec/brawl/helper_spec.rb +23 -0
- data/spec/brawl/parts/basic_motor_spec.rb +220 -0
- data/spec/brawl/parts/basic_scanner_spec.rb +101 -0
- data/spec/brawl/parts/basic_weapon_spec.rb +146 -0
- data/spec/spec_helper.rb +5 -0
- metadata +101 -10
data/.gitignore
CHANGED
data/.rspec
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Brawl
|
2
|
+
|
3
|
+
A dangerous robotic programming game that will kill you in your sleep.
|
4
|
+
|
5
|
+
Write robotic assassins using Ruby and watch them as they do your evil bidding.
|
6
|
+
|
7
|
+
## Status
|
8
|
+
**Alpha Development**
|
9
|
+
|
10
|
+
Brawl is a work in progress but currently supports running bots on your local machine with limited security.
|
11
|
+
|
12
|
+
## Features
|
13
|
+
The framework is designed from the ground up to be extendable. Want to add some new functionality to robots like dropping land mines, semi-intelligent turrets, or heat seeking missiles? Just write a new part and load it into your bot.
|
14
|
+
|
15
|
+
## Security
|
16
|
+
Brawl was also designed to be secure (when the DRb methodology is implemented). In the secure version (in prototype stage now) bot code runs in a $SAFE = 3 process fork and communicates via a wrapper that uses DRb to talk to a proxy object.
|
17
|
+
|
18
|
+
Whew, that was a mouth full but what it basically means is you can feel reasonably safe running other people's code on your machine. They won't be able to erase your hard drive, probably, much less hack your bots or the game engine to cheat.
|
19
|
+
|
20
|
+
Legal Notice: This is not a guarantee of fitness or anything else. If you use other people's code without understanding what it does expect your entire system to melt.
|
21
|
+
|
22
|
+
## Example bot programs
|
23
|
+
|
24
|
+
This is a really dumb robot that just scans for enemies and shoots at them. If it can't find an enemy it will move around till it does. How would you improve it?
|
25
|
+
|
26
|
+
code do |bot|
|
27
|
+
@dir ||= 0
|
28
|
+
targets = bot.scan direction: @dir, angle: 90
|
29
|
+
@dir += 45
|
30
|
+
@dir %= 360
|
31
|
+
unless targets.each do |target|
|
32
|
+
unless target[:class] == Brawl::Wall
|
33
|
+
results = shoot(target[:bearing])
|
34
|
+
end
|
35
|
+
end.empty?
|
36
|
+
else
|
37
|
+
if rand(0) > 0.5
|
38
|
+
bot.turn [:left,:right,:around].sample
|
39
|
+
end
|
40
|
+
bot.move rand(3) + 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,98 @@
|
|
1
|
+
Brainstorming how it's all going to fit together.
|
2
|
+
I'll probably toss a lot of this as I get into coding but this helps me think about the problem.
|
3
|
+
|
4
|
+
|
5
|
+
Code loader
|
6
|
+
Encapsulates code input.
|
7
|
+
|
8
|
+
This could be from a file, a webpage, a database, etc.
|
9
|
+
|
10
|
+
|
11
|
+
BasicRobot
|
12
|
+
Defines the minimum objects a robot should have.
|
13
|
+
|
14
|
+
For instance it should have a motor, a scanner, a weapon, and a shield
|
15
|
+
|
16
|
+
|
17
|
+
BasicObject?
|
18
|
+
Should I define a basic object or objects like weapons, motors, scanners, or just a basic object that can interact with the world.
|
19
|
+
|
20
|
+
It could have properties like x,y... that couples it to the Area though doesn't it?
|
21
|
+
|
22
|
+
|
23
|
+
RobotSpecs
|
24
|
+
A model that describes how many points are assigned to each component of a robot.
|
25
|
+
|
26
|
+
Does this even need to be a class? It's basically just an array. Maybe this can encapsulate how to read that using given Data API.
|
27
|
+
|
28
|
+
|
29
|
+
RobotRules
|
30
|
+
This model defines how many points mean what in a RobotSpec.
|
31
|
+
|
32
|
+
For instance how much damage a Laser set to 8 does or how much damage a shield set to 4 mitigates. This is how the game will be balanced.
|
33
|
+
|
34
|
+
|
35
|
+
Code parser/DSL
|
36
|
+
Handles converting raw bot code into runnable code.
|
37
|
+
|
38
|
+
|
39
|
+
Code runner
|
40
|
+
Runs the code in a safe manner.
|
41
|
+
|
42
|
+
This will most likely be by containing it in a tread with $SAFE = 4.
|
43
|
+
|
44
|
+
|
45
|
+
Communication Layer
|
46
|
+
Sends messages back and forth between the arena and the code runner.
|
47
|
+
|
48
|
+
This could be as simply as wiring up the receive to the transmit and vice versa for local running to a web based interface using Sinatra.
|
49
|
+
It should handle communication from multiple clients securely (a bot shouldn't be able to lie about who it is)
|
50
|
+
|
51
|
+
|
52
|
+
BotController
|
53
|
+
This controller is the glue that pushes all the buttons of the classes needed to make a bot run like: Code loader, Code parser, a communication layer, and the code runner.
|
54
|
+
|
55
|
+
|
56
|
+
ArenaController
|
57
|
+
Orchestrates all the classes needed to make combat work like Layouts, Arena and Bot Rules, and ScoreKeeping.
|
58
|
+
|
59
|
+
|
60
|
+
ArenaLayout
|
61
|
+
The model that describes what an arena looks like.
|
62
|
+
|
63
|
+
How long and wide it is and where obstacles are.
|
64
|
+
|
65
|
+
|
66
|
+
ArenaRules
|
67
|
+
A model that contains rules for battles.
|
68
|
+
|
69
|
+
This could be things like how many battles to fight, the maximum number of points allowed for a bot, time limits, etc.
|
70
|
+
|
71
|
+
|
72
|
+
ScoreKeeping
|
73
|
+
Stores the results of battles.
|
74
|
+
|
75
|
+
|
76
|
+
Data API
|
77
|
+
Basic interface to datastores.
|
78
|
+
|
79
|
+
This could be a JSON or YAML file or a database. This should be injected into a model so the model knows how to read data.
|
80
|
+
|
81
|
+
|
82
|
+
Config
|
83
|
+
Bootstraps the basic configuration for the ApplicationController.
|
84
|
+
|
85
|
+
This might contain server locations, DataAPI types, and communication layer types.
|
86
|
+
|
87
|
+
|
88
|
+
Application Controller
|
89
|
+
This is what orchestrates all the other classes into a usable application.
|
90
|
+
|
91
|
+
This could be a CLI 'bot developer client or server or it could decide what it is by the way you run it. It could run a GUI.
|
92
|
+
|
93
|
+
|
94
|
+
BattleView
|
95
|
+
Displays information about a running battle and its results.
|
96
|
+
|
97
|
+
|
98
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# Brainstorming what a bot program might look like so I can figure out how to deal with it
|
2
|
+
|
3
|
+
# I've removed the idea of throttling. Throttling changes the model from event based
|
4
|
+
# to event/queue based so then there's no point in multi-threading in the first place.
|
5
|
+
|
6
|
+
# Bots are divided into classes by points and attachments (mixin modules that add more functionality)
|
7
|
+
# A basic bot might be allowed 10 points to allocate. The more points the better the system of course.
|
8
|
+
#
|
9
|
+
|
10
|
+
# This defines your robot's characteristics
|
11
|
+
Bot.build
|
12
|
+
{
|
13
|
+
# required
|
14
|
+
scanner: 2,
|
15
|
+
motor: 2,
|
16
|
+
laser: 2,
|
17
|
+
shield: 4,
|
18
|
+
# optional
|
19
|
+
name: "Full function bot",
|
20
|
+
author: "Mike Bethany",
|
21
|
+
description: "A simple but full featured robot to show all the capabilities",
|
22
|
+
revision: '0.0.1',
|
23
|
+
}
|
24
|
+
# Should there be some kind of GUID handed out by a central command to identify bots?
|
25
|
+
|
26
|
+
# These are things that can happen and should be responded to
|
27
|
+
event :enemy_found
|
28
|
+
event :enemy_lost
|
29
|
+
|
30
|
+
# This loop looks for an enemy and tries to track it
|
31
|
+
# (loop creates a thread)
|
32
|
+
@contact = nil
|
33
|
+
BasicScanner.loop do
|
34
|
+
# try to keep the scanner locked on the enemy
|
35
|
+
if @contact and @contact.is_enemy?
|
36
|
+
direction, sweep, range = contact.direction - 5, 10, @contact.range + 2
|
37
|
+
else
|
38
|
+
# lazy scanning
|
39
|
+
# It's very slow since it's scanning the maximum range in a full circle
|
40
|
+
direction, sweep, range = 0, 360, :max
|
41
|
+
end
|
42
|
+
@contact = scan direction: direction, sweep: sweep, range: range
|
43
|
+
end
|
44
|
+
|
45
|
+
# Another scanner thread that is a lot faster and just looks for walls
|
46
|
+
BasicScanner.loop do
|
47
|
+
if check_for_walls(:front)
|
48
|
+
[:left, :right].each do |direction|
|
49
|
+
@walls[direction] = check_for_walls(direction)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
fire_event(:wall)
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_for_walls(direction)
|
56
|
+
# This is a really fast scan because it's scanning the minimum distance at only 1 degree
|
57
|
+
!BasicScanner.scan direction: direction, sweep: 1, range: Motor.speed
|
58
|
+
end
|
59
|
+
|
60
|
+
BasicScanner.loop do
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# Another low priority thread that moves the bot around randomly
|
65
|
+
Motor.loop do
|
66
|
+
|
67
|
+
if Motor.dead_end
|
68
|
+
reverse
|
69
|
+
Motor.dead_end = false
|
70
|
+
else
|
71
|
+
# mostly go straight but turn randomly sometimes
|
72
|
+
unless rand(100) > 70 and check_for_wall(:front)
|
73
|
+
forward unless Motor.direction == :forward
|
74
|
+
else
|
75
|
+
# turning takes time and is blocking
|
76
|
+
Motor.dead_end = random_turn
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def random_turn
|
83
|
+
directions = [:left,:right]
|
84
|
+
while pick = directions.delete(rand(directions.length))
|
85
|
+
unless check_for_wall(pick)
|
86
|
+
turn pick
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
!directions.is_empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
# This does not create a thread/loop but instead dynamcially creates a method using the given block
|
94
|
+
# You can call it whatever you want as long as you don't pick something that's already taken.
|
95
|
+
# If you try to overwrite methods that are important to the bot like forward or reverse they
|
96
|
+
# won't be used since this would break the system's ability to interact with your bot.
|
97
|
+
Laser.engage do |contact|
|
98
|
+
# lazy aiming (a better approach might be to remember the last few contacts and try to extrapolate location)
|
99
|
+
|
100
|
+
# Aim sends the command to move the turrent but it's a non-blocking call
|
101
|
+
aim contact.direction
|
102
|
+
|
103
|
+
# Since aim is a non-blocking call you might not actually be pointed at the enemy yet
|
104
|
+
fire if contact.distance <= Laser.range and BasicScanner.scan direction: contact.direction, sweep: 3, range: contact.distance, priority: 5
|
105
|
+
end
|
106
|
+
|
107
|
+
# Again this method is created dynamically using the given block
|
108
|
+
Motor.pursue do |contact|
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
|
data/_design/array.rb
ADDED
data/_design/threads.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
Thread.abort_on_exception = true
|
2
|
+
|
3
|
+
class Time
|
4
|
+
def self.time
|
5
|
+
Time.now.strftime("%H:%M:%S.%L")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Kernel
|
10
|
+
def tputs(message)
|
11
|
+
puts "#{Time.time} #{message}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Resource
|
16
|
+
@mutex = Mutex.new
|
17
|
+
|
18
|
+
def self.do(count, time)
|
19
|
+
tputs "do: #{count}"
|
20
|
+
count.times do |index|
|
21
|
+
self.use(count, time, index)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.use(count, time, index)
|
26
|
+
@mutex.synchronize do
|
27
|
+
timeout = Time.now + time
|
28
|
+
sleep(0.01) while Time.now < timeout
|
29
|
+
tputs "#{index} for count: #{count}"
|
30
|
+
end
|
31
|
+
# This sleep seems to be crucial. The mutex is released but if I
|
32
|
+
# don't have a sleep here the next thing waiting for the mutex
|
33
|
+
# doesn't seem to get the resource.
|
34
|
+
sleep(0.001)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Worker
|
40
|
+
|
41
|
+
def initialize(&block)
|
42
|
+
@block = block
|
43
|
+
end
|
44
|
+
|
45
|
+
def start
|
46
|
+
@stop_working = nil
|
47
|
+
tputs "Starting: #{@block}"
|
48
|
+
Thread.new do
|
49
|
+
until @stop_working
|
50
|
+
@block.call
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop
|
56
|
+
@stop_working = false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#slow = Worker.new {Resource.do_something(5,0.2)}
|
61
|
+
#fast = Worker.new {sleep(0.5);Resource.do_something(1,0.1)}
|
62
|
+
slow = Worker.new {Resource.do(3,0.25)}
|
63
|
+
fast = Worker.new {sleep(1.5); Resource.do(1,0.1)}
|
64
|
+
slow.start
|
65
|
+
fast.start
|
66
|
+
stop = Time.now + 5
|
67
|
+
sleep(0.01) while stop > Time.now
|
68
|
+
|
69
|
+
slow.stop
|
70
|
+
fast.stop
|
71
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Brawl::Communications::Linker do
|
4
|
+
|
5
|
+
it "should link two classes using a communication class"
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
class Foo
|
10
|
+
def bar(value)
|
11
|
+
"doh!"
|
12
|
+
end
|
13
|
+
def baz(value1, value2)
|
14
|
+
"Oh, noos!"
|
15
|
+
end
|
16
|
+
def qux
|
17
|
+
"Fudge!"
|
18
|
+
end
|
19
|
+
def quux(value1, value2, &block)
|
20
|
+
"Nope, nope, nope"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class FooProxy
|
25
|
+
def bar(value)
|
26
|
+
"#{value}"
|
27
|
+
end
|
28
|
+
def baz(value1, value2)
|
29
|
+
"#{value1}, #{value2}"
|
30
|
+
end
|
31
|
+
def qux
|
32
|
+
yield "FooProxy"
|
33
|
+
end
|
34
|
+
def quux(value1, value2, &block)
|
35
|
+
yield ["FooProxy", "#{value1}, #{value2}"]
|
36
|
+
end
|
37
|
+
end
|
data/_spike/DRb/arena.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Arena
|
2
|
+
attr_accessor :counter
|
3
|
+
def initialize
|
4
|
+
@counter = ""
|
5
|
+
@bots = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_bot(client_id)
|
9
|
+
@bots[client_id] = {location: 0}
|
10
|
+
end
|
11
|
+
|
12
|
+
def move(value, client_id)
|
13
|
+
puts "move called: #{value.inspect}; #{client_id}"
|
14
|
+
@bots[client_id][:location] += value.first
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class ArenaProxy
|
20
|
+
|
21
|
+
attr_accessor :client_id
|
22
|
+
|
23
|
+
def initialize(arena, client_id=nil)
|
24
|
+
@arena = arena
|
25
|
+
@client_id = client_id
|
26
|
+
end
|
27
|
+
|
28
|
+
def move(amount)
|
29
|
+
@arena.move(amount, client_id)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|