From: <cd...@us...> - 2007-01-09 01:15:33
|
Revision: 1 http://eos-game.svn.sourceforge.net/eos-game/?rev=1&view=rev Author: cduncan Date: 2007-01-08 17:15:30 -0800 (Mon, 08 Jan 2007) Log Message: ----------- Initial checkin -- woot! Added Paths: ----------- data/ data/ship.png eos.py game.py ideas/ ideas/engines.txt ideas/races.txt ideas/weapons.txt player.py projectile.py scimitar.py sprite.py stars.py util.py Added: data/ship.png =================================================================== (Binary files differ) Property changes on: data/ship.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: eos.py =================================================================== --- eos.py (rev 0) +++ eos.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,14 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Main program script +# $Id$ +# $Date$ + +import game + +if __name__ == '__main__': + game.init() + game.play() + Property changes on: eos.py ___________________________________________________________________ Name: svn:eol-style + native Added: game.py =================================================================== --- game.py (rev 0) +++ game.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,64 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Global game elements +# $Id$ +# $Date$ + +from random import randint +import pygame +from pygame.locals import * +from player import Player +from stars import StarField +from sprite import RenderedGroup + + +screen = None # Game screen +background = None + +# Sprite groups +sprites = None # All sprites + +# Other globally accessible things +stars = None +clock = None +player = None + +def init(): + global screen, background, sprites, stars, clock, player + pygame.init() + # Setup window + screen = pygame.display.set_mode((800,800)) + pygame.display.set_caption('Eos') + background = pygame.Surface(screen.get_size()) + background.fill((0,0,0)) + stars = StarField(screen) + # Display the background right away + screen.blit(background, (0, 0)) + pygame.display.flip() + + # Create game elements + clock = pygame.time.Clock() + sprites = RenderedGroup() + player = Player() + +def handle_events(): + for event in pygame.event.get(): + if event.type == QUIT or ( + event.type == KEYDOWN and event.key == K_ESCAPE): + return False + return True + +def play(): + while handle_events(): + sprites.clear(screen, background) + stars.clear(background) + stars.move(player.vx, -player.vy) + dirty = stars.draw() + sprites.update() + dirty += sprites.draw(screen) + pygame.display.update(dirty) + pygame.display.flip() + clock.tick(60) + Property changes on: game.py ___________________________________________________________________ Name: svn:eol-style + native Added: ideas/engines.txt =================================================================== --- ideas/engines.txt (rev 0) +++ ideas/engines.txt 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,151 @@ +Main Engines +============ + +Impulse Engine +-------------- +The mainstay of sub-light propulsion technology for nearly a century, the venerable impulse engine is ubiquitous throughout known space. Requiring virtually no maintenance, they propel everything from courier craft to hulking battle frigates. Impulse engines provide equally adequate thrust and velocity, making them some of the most popular all-around engines. At low speeds, impulse engines are fairly economical to run, though their efficiency drops dramatically as speed increases. Even though periodic maintenance is not required, it can greatly enhance the longevity of the engine. Many a freighter captain have been stranded when their impulse drives failed prematurely due to neglect. + +Stationary Thrust: 1 +Max Thrust: 1@0 +Speed Threshold: 400 +Power Drain: 1 (max) +Mass: 4 tons +Cost: 50,000cr + +Ion Engine +---------- +Research into improving the high-speed efficiency of impulse drive lead to the development of ion engine technology. Using a beam of highly charged molecules, these engines can deliver an order of magnitude better efficiency at high speed over their impulse counterparts. This high-speed efficiency comes at the cost of a much lower power/weight ratio, hampering accelleration. Erosion of the electrostatic grid used to accellerate the charged particles requires periodic maintenance, but the manufacturer guarantees 5000 hours of operating time between services. + +Stationary Thrust: 0.5 +Max Thrust: 1@600 +Speed Threshold: 1200 +Power Drain: 0.5 (max) +Mass: 10 tons +Cost: 80,000cr + +MPD Thruster +------------ +Gerundive enterprises took a big risk developing the magneto-plasma-dynamic thruster technology. Early failures resulted in them becoming a near laughing-stock of the industry, as many startups before them had tried and failed. After hiring the brilliant physicist Just Van Russo, however, the project breathed new life and in just 18 short months a working prototype was ready for trials. This first generation product is nearly unchanged from the prototype, save fitment of industry standard mounts and interfaces. Even in this early stage the technology shows tremendous promise supplying both high accellerative thrust and terminal velocity. Their tremendous energy consumption mandates onboard power generation, therefore limiting their usefulness on smaller ships. The cathode which generates the tremendous magnetic field will also degrade over time, though it should remain viable for around 8000 operating hours. + +Stationary Thrust: 1.5 +Max Thrust: 2@300 +Speed Threshold: 800 +Power Drain: 3 (max) +Mass: 5 tons +Cost: 200,000cr + +M2P2 +---- +Science-fiction has long envisioned craft sailing effortlessly on the solar wind. Efforts to create solar sails made of reflective materials were of limited success and suffered from low efficiency, high maintenance and poor lifespan. Mini-magnetospheric plasma propulsion or M2P2 was designed to harness the power of stars using a conductive plasma contained in a magnetic field. This plasma creates a synthetic magnetosphere whose size varies with the density of the prevailing solar wind thereby maintaining the same thrust regardless of the distance from the star. These magnetic sails can also maneuver using the magnetic fields of nearby planets or the plasma inside a nebula. Only a small energy field is required to contain the inert plasma used to catch the charged particles, thus energy consumption is extremely low, though never zero, even at rest. The theoretical top-speed of M2P2 is close to the speed of light itself inside star systems, however accelleration falls off quickly as speed increases. At lower speeds, however accelleration is quite adequate, and is aided by the low mass of the drive mechanism. Over time the inert plasma can become poisoned by stellar particles, reducing efficiency. The manufacturer recommends inert gas replacement every 2000 operating hours, though many space-farers are known to go much longer between services. M2P2 is incompatible with other drive systems, thus must be fitted exclusively on a given ship. + +Stationary Thrust: 0.75 +Max Thrust: 1@100 +Speed Threshold: 800 +Power Drain: 0.33 (constant) +Mass: 2 tons +Cost: 150,000cr + +Graviton Diametric Drive +------------------------ +The GDD harnesses an artificial asymmetric gravitational field to induce motion into a stationary mass. The ship literally falls toward the field center, which is kept a fixed distance away in the desired direction of motion. The accelleration is proportional to the field strength, though doubling the field strength increases energy consumption four-fold. The terminal velocity is quite high, though still firmly in the sublight range due to decreased field stability at speed. The power requirements to maintain the gravitational field are staggering and mass of the drive unit is substantial, but the resulting field may be projected in any desired direction, thus eliminating the need for a separate maneuvering system other than directional thrusters. GDD requires relativistic navigational instruments, which make it incompatible with other drive units. The powerful field generated requires expert care and handling to avoid compromising the structural intregity of the ship. Only a helmsman first-class or higher is licensed to operate GDDs safely. It is assumed, of course, that under such professional supervision, that no regular maintenance will be required. + +Stationary Thrust: 10 +Max Thrust: 10@0 +Speed Threshold: 800 +Power Drain: 8 (max) +Mass: 25 tons +Cost: 2,000,000cr + +Quantum Differential Drive +-------------------------- +Harnessing the energy of the cosmic background radiation itself, QDDs are a true marvel of engineering achievement. By creating a difference in radiation pressure via a special isotropic medium, thrust is generated with nearly 100% energy efficiency. Unfortunately, dense concentrations of matter or energy near the drive unit interfere significantly with the quantum fluctuations, thus QDDs are unsuitable for massive vessels and cannot be used in conjunction with other types of main engines. On the bright side, motion through space actually stabilizes the quantum flux, making these some of the best performing engines at high speed. Their high performance, small mass and energy efficiency makes them ideal for light and medium vessels of all types. The isotropic medium does erode over time, wearing fastest at high power levels. It is assured, however, that with regular service every 10,000 operating hours, the drive system should never require overhaul. + +Stationary Thrust: 1 +Max Thrust: 1.5@1000 +Speed Threshold: 1500 +Power Drain: 0.75 (max) +Mass: 3 tons +Cost: 750,000cr + +Spacial Dilation Engine +----------------------- +All other sub-light engines operate under the principle of Newton's second law of motion, where an unbalanced force generates momentum through space. The spacial dialation engine works instead by inducing a local spacial distortion which actually moves the fabric of space-time itself relative to the vessel. Outside the ship, normal motion is observed, but the ship has not been inparted with any momentum whatsoever. What has in fact happened is a realignment of the crystalline geometry of space-time at a quantum level surrounding the vessel. As a result the ship travels with no inertia and experiences no accelleration. The energy required to perform the spacial dialation is immense, and increases geometrically with the physical size of the vessel and the velocity of travel. Since there is no accelleration, the mass of the vessel is insignificant (unless the vessel is massive enough to generate a sizeable gravitational field itself) and the time to achieve terminal velocity is limited only by the electrical discharge capacity of the ship. The maximum speed is nearly the speed of light, limited not by the ship itself which is not in motion in the traditional sense, but by the drag of particles at the periphery of the dilation field. Because there is no inertia, a constant supply of power must be administered to maintain speed, however, changes in course can be executed instantaneously regardless of speed or vessel mass. The spacial dialation field can be finely controlled to execute any desired maneuvers, thus rendering separate maneuvering thrusters unnecessary. Spacecraft must be designed around the SDE, since its principles of operation are so unlike other engines. + +Stationary Thrust: 10 +Speed Threshold: None +Power Drain: 20 (max) +Mass: 20 tons +Cost: 10,000,000cr + +Engine Enhancements and Spares +============================== + +Max Burn +-------- +Impulse engines lost their pulse? Ion erosion got you down? Max burn is clinically proven by 4 out of 5 space jockeys to reduce engine wear and maximize your maintenance dollar. Nothing else comes close to these claims, and remember no other product is clinically proven! Use with impulse or ion engines only, void where prohibited, results not typical, past performance no guarantee of future sucess, your mileage may vary. + +Cost: 5,000cr + +Engine Overhaul +--------------- +Even with regular maintenance, engine performance degrades over the life of the drive system. An overhaul will restore your drive system to its full potential. Depending on the age and maintenance history of the engine, an overhaul will restore the system to within 70-90% of its performance when new. Each successive overhaul is less effective than the last, but given the cost of engine replacement, its an economical way to get the most from your propulsion system. + +Cost: 25% of engine cost + +Main Engine Tune-up +------------------- +Impulse, ion and MPD thrusters all lose performance over time due to contaminent build-up and misalignment of internal core components. A tuneup will remove contaminents and adjust the components to within factory specifications. Not only will this restore lost performance, but can greatly increase the longevity of your engines, especially for impulse drives. A tune-up is no substitute for an overhaul, but if performed often enough can greatly increase the time between major services. + +Cost: 10,000cr per engine + +Engine Inspection +----------------- +Worried that your engine performance is not all it could be? Don't know whether you need an overhaul or if just a simple tune-up will do? Our expert technicians will pour over your engines and give their assessment of performance, comparing it with new engines of the same type. They'll also estimate the performance gained by a tune-up or overhaul as well as the remaining lifespan of your propulsion system. + +Cost: 2,000cr per engine + +Propulsion Benchmark +-------------------- +Want to see how your vessel really performs? Benchmarking your main engines will show you exactly what their capabilities are in terms of accelleration throughout a wide range of speeds and effective top speed, as well as time taken to reach it. You can also compare your vessel against others to see how its performance stacks up. + +Cost: 5,000cr + +Afterburner +----------- +Coupled to the exhaust of a main engine, an afterburner provides a burst of accelleration and speed on demand. A staple in racing circles and law enforcement, afterburners are also used by freighters to improve of-the-line accelleration and maneuverability. Afterburners are also popular with smugglers and other miscreants who occasionally find themselves persued. The power drain is substantial when in use, thus they are best activated in short bursts. The mass of the unit trades some maneuverability for top end speed, but this is compensated for by the boost in accelleration provided. Compatible with impulse, ion, MPD and QDD drives. + +Mass: 2 tons per engine + +Maneuvering Fitments +==================== + +Thrust Vectoring +---------------- +A simple manuvering system that simply redirects up to 20% of the thrust of your main engines to impart rotational momentum on your ship. Though lightweight, the vectoring vanes do reduce the particle velocity of the main engines, thereby reducing top-speed by 10-15%. The thrust vectoring system only functions under main engine power. Compatible with impulse, ion and MPD thrusters. + +Mass: 0.5 ton per engine. + +Maneuvering Thrust Conduits +--------------------------- +These simple manuevering thrusters siphon thrust from your main engines and redirect it to various ports on the periphery of your ship. Up to 20% of your main engine thrust can be directed through the maneuvering conduits. Though lighter than PPM thrusters, the conduits and valves increase turbulence in the main engine plumbing, resulting in about a 15% reduction in accellerative thrust. Compatible with impulse, ion, MPD and QDD drives. + +Mass: 1 tons + +Pulsed Plasma Maneuvering Thrusters +----------------------------------- +A popular stand-alone maneuvering system, PPM thrusters utilize an arc of electric current to produce quick bursts of directed impulse energy. Though heavier than thrust conduits, they require no performance compromising main engine modifications. Accelleration is excellent at low speeds, though they are notoriously inefficient, draining power at a prodigious rate. + +Mass: 4 tons + +Pulsed Plasma Directional Thrusters +----------------------------------- +Similar to the pulsed plasma maneuvering system, this simplified design includes thrusters only for directional control, thus is only half the mass of the full system. The same caveats of accelleration and operational efficiency apply as with PPN thrusters. Spacers with main engine systems that provide their own manueverig capabilities often add on directional thrusters for additional maneuverability. + +Mass: 2 tons + +Company Names +============= +Gerundive Enterprises +Futurity Industries + + Property changes on: ideas/engines.txt ___________________________________________________________________ Name: svn:eol-style + native Added: ideas/races.txt =================================================================== --- ideas/races.txt (rev 0) +++ ideas/races.txt 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,79 @@ +Eos -- Races Overview + +Human +===== +- Just developed FTL tech +- Does not know about other races +- Solar system has large amounts of resources, though these tend to be + squandered on decadent pursuits +- More dependant on matter resources +- Military tech depends on moderately expensive, moderately powerful units +- Moderately aggressive, though may choose to fight politically or + economically rather than via force +- Medium regard for life +- Medium lifespan, Medium population growth +- Strongly individualistic, tend toward factions +- Interested in justice when it serves the winner +- Sexes treated relatively equally + +Main Characters +--------------- +Captain Ben Nakos (American, your superior at game inception) +Leutenant Daria Anders (Northern European, your tutor early on) +Admiral Ibo Takahashi (Japanese, Commander of the Solar Coalition navy) + +Naree +===== +- Highly technolgically advanced +- Civilization is past its prime, political willpower lacking, resources + becoming expended +- Knows about humans and is awaiting contact, does not feel threatened by them + (or anyone) +- At war with the Yeqon, though too politically impotent to go on the + offensive, and do not admit to themselves the obvious threat they pose. + Currently holding them at bay in a stalemate. +- More dependant on energy resources +- Military tech centers on strategic use of expensive, high powered units +- High regard for life +- Long lifespan, Low population growth +- Not indivdualistic, though each individual strives to reach enlightenment. + Heavily invested in the notion of fairness and justice. +- Lack distinct sexes, Naree reproductive process unknown, as is their + lifespan + +Main Characters +--------------- +- Kti (A wise Nareen who wishes to befriend humanity, your guide in the Naree + storyline) +- Matma (A skeptical Nareen who thinks the humans are secretly pawns of the + Yeqon) +- Dalini (The "mother" of the Naree (although not exactly female), their + spiritual and political leader. Strong on philosophy, weak on action. + Views other races with some indifference which could also be viewed as + arrogance) + +Yeqon +===== +- Highly aggressive +- Matriarcy, the females dominate positions of power and make all decisions. + Men are simply ultilized as blunt instruments, both at war and otherwise ;^) +- Men lack any sort of individuality, and fight to maintain the honor of their + corps. The notion of individual honor or justice does not exist. +- More dependent on chi resources +- No regard for individual life +- Short lifespan, high population growth +- Persue knowledge only as it aids their cause in battle, therefore their + technology is heavily slanted toward making war and little else +- At war with the Naree, obsessed with their destruction and pay attention to + little else. +- Military tech centers on large numbers of inexpensive units + +Main Characters +--------------- +- Pazuzu (corps commander (male), your superior in the Yeqon storyline) +- Raiju (Yeqon military commander (female), one of the few Yeqon females that + actually engage directly in combat operations. A deadly adversary) +- Zegtji (Yeqon Queen. Bent on the destruction of the Naree, and later + incensed by the decadent humans) + + Property changes on: ideas/races.txt ___________________________________________________________________ Name: svn:eol-style + native Added: ideas/weapons.txt =================================================================== --- ideas/weapons.txt (rev 0) +++ ideas/weapons.txt 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,152 @@ + +Weapon Types +============ + +Beams (Directed Energy) +----------------------- +- Beam weapons fire in a (typically straight) line from the source in the + targeted direction at a more or less infinite velocity. +- The beam hits the closest object in the line of fire between the source and + the beams maximum range. +- Beams draw down ship energy. +- Beams are unaffected by ship acceleration or velocity. +- Beams may be scattered by dust or dense gases in nebulas reducing their effectiveness. +- Beams have no mass or momentum. + +Projectiles +----------- +- Projectiles are fired in a (typically straight) line from source to target + at a fixed velocity. +- Projectiles hit the closest object in the line of fire between the source and + its maximum range. +- Projectiles always start at the same velocity relative to the motion of the + ship. +- Projectiles may lose velocity as they travel. +- Dust and gases do not affect projectile motion. +- Projectile weapons require ammunition which may be manufactured on-ship at a fixed + rate or loaded before launch. +- The weapon can only hold a fixed amount of ammunition. +- Projectiles have mass and transfer their momentum into whatever they hit. + +Missles +------- +- Missiles are miniature craft with engines. Their flight dynamics are exactly + like space craft with respect to thrust, speed and mass. +- Missiles begin flight at the same speed and direction as their source. They + accelerate to their top speed just like space craft. +- Missiles may maneuver like ships, guided by various mechanisms to their + target. +- Missiles contain a warhead which explodes when it strikes any obstacle, + or the missile expends its energy. +- Missiles may be targeted and destroyed like ships. Missile warheads do + not detonate if the missile is destroyed. +- Missiles have mass and transfer their momentum into whatever they hit. +- Missiles may be loaded before launch or manufactured on ship. +- Missiles must be charged when they are loaded before they can be launched. + +Bombs +----- +- Bombs are similar to missiles, but have no engines. +- Bombs are launched at a fixed velocity relative to their source. +- Bombs may be selective, only detonating when they contact their target + or after a fixed period of time. +- Bomb explosions may launch shrapnel either in all directions or toward + their direction of travel that act like many individual projectiles. +- Bombs have a fixed amount of momentum and do not accelerate. They may + maneuver, but only at the cost of their existing momentum. +- Bombs may be targeted and destroyed like ships. Bombs do not detonate + when destroyed. +- Bombs have mass and transfer their momentum into whatever they hit. +- Bombs may be loaded before launch or manufactured onboard. + +Weapons Systems +=============== + +Cannon +------ +Cannons are fixed-mount weapons which house either beam or projectile weapons. A cannon can be aimed off axis 15 degrees in each direction for precision targeting. + +Turret +------ +Turrets mount beam, projectile or missile weapons on a rotating platform. This platform allows aiming the weapon over a wide arc. Turrets may mount multiple weapons and are often armored. Turrets can hold more ammunition than their fixed mounted counterparts. Large turrets require dedicated crew to operate. + +Missile Rack +------------ +Missile launchers are fixed position systems that attach missiles externally, parallel to the vessel. These launchers provide interfaces for programming, energizing and targeting missiles. Different missile types require different launch racks. Racks vary in the number of missiles they can carry and must be loaded prior to launch. + +Torpedo Tube +------------ +Torpedo tubes are inboard launchers for torpedo-type missiles. Although they occupy more physical space and are more massive than comparable external missile launchers, they can hold more ammunition and reload in flight. Torpedo tubes also provide an acceleration boost allowing more effective backward launching and faster interception. A single tube can launch multiple different munitions, though it takes time to switch munitions in-flight. Torpedo tubes for large munitions require dedicated crew. + +Bomb Rack +--------- +A fixed system for loading and launching bombs of various types. Relatively lightweight, holds a small number of bombs, must be loaded prior to launch. Bombs are launched with a small boost in the direction the vessel is pointing. + +Bomb Launch Bay +--------------- +An internal bomb release system which is larger and more massive than a fixed bomb rack, but holds more munitions. Bomb launch bays allow bombs to be targeted across a wide arc, allowing bombers more tactical flexibility. Like torpedo tubes, a bomb launch bay can launch multiple different bomb types. All bomb bays require dedicated crew. + +Beam Weapons +============ + +Light Laser Cannon +------------------ + +Light Laser Turret +------------------ + +Laser Point-Defense Turret +-------------------------- + +Laser Lance +------------ + +Projectile Weapons +================== + +Light Plasma Cannon +------------------- + +Light Plasma Turret +------------------- + +Plasma Point-Defense Turret +--------------------------- + +Pulsed Plasma Cannon +-------------------- + +Pulsed Plasma Turret +-------------------- + +Heavy Plasma Gun Platform +------------------------- + +Vulcan Percussive Cannon +------------------------ + +Vulcan Percussive Turret +------------------------ + +Percussive Point-Defense Turret +------------------------------- + +Light Ion Cannon +---------------- + +Ion Cannon +---------- + +Ion Cannon Turret +----------------- + +Heavy Ion Cannon +---------------- + +Fullerene Pulse Cannon +---------------------- + +Fullerene Defense System +------------------------ + + Property changes on: ideas/weapons.txt ___________________________________________________________________ Name: svn:eol-style + native Added: player.py =================================================================== --- player.py (rev 0) +++ player.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,65 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Player state +# $Id$ +# $Date$ + +import pygame +from pygame.locals import * +import game +from util import unit_vector +from projectile import FullereneCannon + +class Player(pygame.sprite.Sprite): + + fw_thrust = 0.1 + bw_thrust = fw_thrust * 0.33 + max_speed = 6 + max_turn_rate = 2 + turn_accel = 0.12 + + def __init__(self): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.ship_img = pygame.image.load('data/ship.png').convert( + game.screen) + self.rect = self.ship_img.get_rect( + center=game.screen.get_rect().center) + self.heading = 0 + self.turn = 0.0 + self.vx = self.vy = 0.0 + self.weapon = FullereneCannon(self) + self.update_ship() + + def update_ship(self): + self.image = pygame.transform.rotate(self.ship_img, self.heading) + self.rect = self.image.get_rect(center=self.rect.center) + return self.rect + + def update(self): + last_heading = self.heading + keystate = pygame.key.get_pressed() + turn_dir = keystate[K_LEFT] - keystate[K_RIGHT] + if turn_dir: + self.turn += turn_dir * self.turn_accel + self.turn = min(max(self.turn, -self.max_turn_rate), self.max_turn_rate) + elif self.turn > 0: + self.turn = max(self.turn - self.turn_accel, 0) + elif self.turn: + self.turn = min(self.turn + self.turn_accel, 0) + self.heading += self.turn + if self.heading != last_heading: + self.update_ship() + + if keystate[K_UP]: + xx, yy = unit_vector(self.heading) + self.vx = max(min(self.vx + xx * self.fw_thrust, self.max_speed), -self.max_speed) + self.vy = max(min(self.vy + yy * self.fw_thrust, self.max_speed), -self.max_speed) + if keystate[K_DOWN]: + xx, yy = unit_vector(self.heading) + self.vx = max(min(self.vx - xx * self.bw_thrust, self.max_speed), -self.max_speed) + self.vy = max(min(self.vy - yy * self.bw_thrust, self.max_speed), -self.max_speed) + + self.weapon.update(keystate[K_SPACE]) + Property changes on: player.py ___________________________________________________________________ Name: svn:eol-style + native Added: projectile.py =================================================================== --- projectile.py (rev 0) +++ projectile.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,83 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Projectile weapons +# $Id$ +# $Date$ + +from random import randint +import pygame +from pygame.locals import * +import game +from util import unit_vector + + +class FullereneCannon: + + charge_rate = 0.1 + max_charge = 4.5 + min_charge = 0.5 + max_hold = 200 + + def __init__(self, gunmount): + self.charging = 0 + self.charge = 0 + self.gunmount = gunmount + + def update(self, charging): + if charging and self.charging < self.max_hold: + self.charging += 1 + self.charge = min(self.charge + self.charge_rate, self.max_charge) + elif self.charging and self.charge >= self.min_charge: + Fullerene(self.charge, self.gunmount) + self.charging = 0 + self.charge = 0 + + +class Fullerene(pygame.sprite.Sprite): + + velocity = 9.0 + range = 450 + color = (255,220,20) + particles = 4 + + def __init__(self, charge, gunmount): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.charge = charge + self.size = max(charge * 2, 2) + x, y = unit_vector(gunmount.heading) + self.vx = -x * self.velocity + self.vy = y * self.velocity + self.distance = 0 + self.partsize = max(self.charge * 1.25, 2) + self.rect = (list(gunmount.rect.center), (self.partsize, self.partsize)) + self.flux = [i * 360.0/self.particles for i in range(self.particles)] + self.colormax = 255 + + def clear(self): + game.screen.fill((0,0,0), self.rect) + + def update(self): + self.rect[0][0] += self.vx + self.rect[0][1] += self.vy + self.distance += self.velocity + if self.range - self.distance < self.velocity * 10: + self.colormax = int(self.colormax * 0.85) + if self.distance >= self.range: + self.kill() + + def draw(self, surface): + rects = [self.rect] + bg = randint(0, self.colormax) + surface.fill((self.colormax, bg, self.colormax-bg) , self.rect) + for i in self.flux: + x, y = unit_vector(self.distance + i) + part = (self.rect[0][0] + x * self.charge, + self.rect[0][1] + y * self.charge, + self.partsize, self.partsize) + rects.append(part) + bg = randint(0, self.colormax) + surface.fill((self.colormax, bg, self.colormax-bg) , part) + return Rect(rects[0]).unionall(rects) + Property changes on: projectile.py ___________________________________________________________________ Name: svn:eol-style + native Added: scimitar.py =================================================================== --- scimitar.py (rev 0) +++ scimitar.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,23 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Scimitar Ship +# $Id$ +# $Date$ + +import pygame +from pygame.locals import * +import game +from util import unit_vector +from projectile import FullereneCannon + +class Scimitar(pygame.sprite.Sprite): + + def __init__(self): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.ship_img = pygame.image.load('data/scimitar.png').convert( + game.screen) + self.rect = self.ship_img.get_rect( + center=game.screen.get_rect().center) + Property changes on: scimitar.py ___________________________________________________________________ Name: svn:eol-style + native Added: sprite.py =================================================================== --- sprite.py (rev 0) +++ sprite.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,39 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Sprite extensions +# $Id$ +# $Date$ + +import pygame + +class RenderedGroup(pygame.sprite.Group): + """A modified version of RenderedUpdates that can render sprites that do + not have an image. If the sprite has no 'image' attribute then it's draw() + method is called passing it the surface. The draw() method returns the + rect affected + """ + def draw(self, surface): + spritedict = self.spritedict + surface_blit = surface.blit + dirty = self.lostsprites + self.lostsprites = [] + dirty_append = dirty.append + for s in self.sprites(): + r = spritedict[s] + if hasattr(s, 'image'): + newrect = surface_blit(s.image, s.rect) + else: + newrect = s.draw(surface) + if r is 0: + dirty_append(newrect) + else: + if newrect.colliderect(r): + dirty_append(newrect.union(r)) + else: + dirty_append(newrect) + dirty_append(r) + spritedict[s] = newrect + return dirty + Property changes on: sprite.py ___________________________________________________________________ Name: svn:eol-style + native Added: stars.py =================================================================== --- stars.py (rev 0) +++ stars.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,80 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Starfield background +# $Id$ +# $Date$ + +from random import randint + + +class StarField: + """Parallax starfield simulation""" + + star_count = 125 + big_star_count = 15 + parallax_depths = 4 + parallax_factor = 0.88 + + def __init__(self, surface): + self.surface = surface + self.rect = surface.get_rect() + self.stars = [ + [float(randint(self.rect.left, self.rect.right)), + float(randint(self.rect.top, self.rect.bottom)), + 1, 1] for i in range(self.star_count) + ] + for i in range(self.big_star_count): + idx = randint(0, self.star_count - 1) + self.stars[idx][2] = 2 + self.stars[idx][3] = 2 + self.draw() + + def clear(self, background): + fill = self.surface.fill + black = (0,0,0) + for star_rect in self.stars: + fill(black, star_rect) + + def draw(self): + fill = self.surface.fill + color = [255,255,255] + left = self.star_count / self.parallax_depths + rects = [] + for star_rect in self.stars: + left -= 1 + if left == 0: + color = [v * 0.75 for v in color] + left = self.star_count / self.parallax_depths + rects.append(fill(color, star_rect)) + return rects + + def move(self, x, y): + leftbound, topbound = self.rect.left, self.rect.top + rightbound, botbound = self.rect.right, self.rect.bottom + width, height = self.rect.width, self.rect.height + speed = 1.0 + left = self.star_count / self.parallax_depths + for star_rect in self.stars: + left -= 1 + if left == 0: + speed *= self.parallax_factor + left = self.star_count / self.parallax_depths + star_rect[0] += x * speed + star_rect[1] += y * speed + # If star moved offscreen, recycle it + # into a new star on the opposite side + if star_rect[0] < leftbound: + star_rect[0] += width + star_rect[1] = randint(topbound, botbound) + elif star_rect[0] > rightbound: + star_rect[0] -= width + star_rect[1] = randint(topbound, botbound) + elif star_rect[1] > botbound: + star_rect[1] -= height + star_rect[0] = randint(leftbound, rightbound) + elif star_rect[1] < topbound: + star_rect[1] += height + star_rect[0] = randint(leftbound, rightbound) + Property changes on: stars.py ___________________________________________________________________ Name: svn:eol-style + native Added: util.py =================================================================== --- util.py (rev 0) +++ util.py 2007-01-09 01:15:30 UTC (rev 1) @@ -0,0 +1,21 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Various utilities +# $Id$ +# $Date$ + +import math + +# Unit circle vector map for each heading in whole degrees +_heading_vec = {} +for d in range(360): + r = math.radians((d + 270) % 360) + _heading_vec[d] = (math.cos(r), math.sin(r)) + +def unit_vector(direction): + """Return a 2D vector as a 2-tuple. Direction is provided in degrees""" + return _heading_vec[int(direction) % 360] + + Property changes on: util.py ___________________________________________________________________ Name: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-01-09 11:54:12
|
Revision: 6 http://eos-game.svn.sourceforge.net/eos-game/?rev=6&view=rev Author: oberon7 Date: 2007-01-09 03:54:08 -0800 (Tue, 09 Jan 2007) Log Message: ----------- Initial pass at ultramega stupid A.I. Modified Paths: -------------- game.py player.py projectile.py util.py Added Paths: ----------- ai.py Added: ai.py =================================================================== --- ai.py (rev 0) +++ ai.py 2007-01-09 11:54:08 UTC (rev 6) @@ -0,0 +1,35 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# A.I. +# $Id$ +# $Date$ + +import random +import pygame +import game +import util + + +class AI(pygame.sprite.Sprite): + + def __init__(self, player): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.player = player + self.ship_img = pygame.image.load('data/ship.png').convert(game.screen) + self.rect = self.ship_img.get_rect(center=game.screen.get_rect().center) + self.heading = random.randint(0, 360) + self.image = pygame.transform.rotate(self.ship_img, self.heading) + self.x = self.y = 0.0 + self.vx, self.vy = util.unit_vector(self.heading) + self.vx *= 2. + self.vy *= 2. + self.update() + + def update(self): + self.x += self.vx + self.y += self.vy + self.rect.centerx = self.player.rect.centerx - self.player.x + self.x + self.rect.centery = self.player.rect.centery - self.player.y + self.y + Modified: game.py =================================================================== --- game.py 2007-01-09 10:13:10 UTC (rev 5) +++ game.py 2007-01-09 11:54:08 UTC (rev 6) @@ -9,6 +9,7 @@ from random import randint import pygame from pygame.locals import * +from ai import AI from player import Player from stars import StarField from sprite import RenderedGroup @@ -24,9 +25,10 @@ stars = None clock = None player = None +ai = None def init(): - global screen, background, sprites, stars, clock, player + global screen, background, sprites, stars, clock, player, ai pygame.init() # Setup window screen = pygame.display.set_mode((800,800)) @@ -42,6 +44,7 @@ clock = pygame.time.Clock() sprites = RenderedGroup() player = Player() + ai = [AI(player) for i in xrange(5)] def handle_events(): for event in pygame.event.get(): @@ -54,7 +57,7 @@ while handle_events(): sprites.clear(screen, background) stars.clear(background) - stars.move(player.vx, -player.vy) + stars.move(-player.vx, -player.vy) dirty = stars.draw() sprites.update() dirty += sprites.draw(screen) Modified: player.py =================================================================== --- player.py 2007-01-09 10:13:10 UTC (rev 5) +++ player.py 2007-01-09 11:54:08 UTC (rev 6) @@ -22,13 +22,11 @@ def __init__(self): pygame.sprite.Sprite.__init__(self, game.sprites) - self.ship_img = pygame.image.load('data/ship.png').convert( - game.screen) - self.rect = self.ship_img.get_rect( - center=game.screen.get_rect().center) + self.ship_img = pygame.image.load('data/ship.png').convert(game.screen) + self.rect = self.ship_img.get_rect(center=game.screen.get_rect().center) self.heading = 0 self.turn = 0.0 - self.vx = self.vy = 0.0 + self.x = self.y = self.vx = self.vy = 0.0 self.weapon = FullereneCannon(self) self.update_ship() @@ -38,8 +36,10 @@ return self.rect def update(self): + keystate = pygame.key.get_pressed() + + # turning last_heading = self.heading - keystate = pygame.key.get_pressed() turn_dir = keystate[K_LEFT] - keystate[K_RIGHT] if turn_dir: self.turn += turn_dir * self.turn_accel @@ -52,6 +52,7 @@ if self.heading != last_heading: self.update_ship() + # thrust if keystate[K_UP]: xx, yy = unit_vector(self.heading) self.vx = max(min(self.vx + xx * self.fw_thrust, self.max_speed), -self.max_speed) @@ -60,6 +61,8 @@ xx, yy = unit_vector(self.heading) self.vx = max(min(self.vx - xx * self.bw_thrust, self.max_speed), -self.max_speed) self.vy = max(min(self.vy - yy * self.bw_thrust, self.max_speed), -self.max_speed) + self.x += self.vx + self.y += self.vy self.weapon.update(keystate[K_SPACE]) Modified: projectile.py =================================================================== --- projectile.py 2007-01-09 10:13:10 UTC (rev 5) +++ projectile.py 2007-01-09 11:54:08 UTC (rev 6) @@ -47,7 +47,7 @@ self.charge = charge self.size = max(charge * 2, 2) x, y = unit_vector(gunmount.heading) - self.vx = -x * self.velocity + self.vx = x * self.velocity self.vy = y * self.velocity self.distance = 0 self.partsize = max(self.charge * 1.25, 2) Modified: util.py =================================================================== --- util.py 2007-01-09 10:13:10 UTC (rev 5) +++ util.py 2007-01-09 11:54:08 UTC (rev 6) @@ -12,10 +12,9 @@ _heading_vec = {} for d in range(360): r = math.radians((d + 270) % 360) - _heading_vec[d] = (math.cos(r), math.sin(r)) + _heading_vec[d] = (-math.cos(r), math.sin(r)) def unit_vector(direction): """Return a 2D vector as a 2-tuple. Direction is provided in degrees""" return _heading_vec[int(direction) % 360] - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-01-09 13:44:55
|
Revision: 7 http://eos-game.svn.sourceforge.net/eos-game/?rev=7&view=rev Author: oberon7 Date: 2007-01-09 05:44:53 -0800 (Tue, 09 Jan 2007) Log Message: ----------- Initial steering behaviors based on Craig W. Reynolds "Boids". Added Paths: ----------- steer.py vector.py Added: steer.py =================================================================== --- steer.py (rev 0) +++ steer.py 2007-01-09 13:44:53 UTC (rev 7) @@ -0,0 +1,39 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Steering behaviors +# $Id$ +# $Date$ + +class Steerable: + """Craig W. Reynolds "Boids" steering behaviors.""" + + def __init__(self, position, velocity, max_speed, turn_speed=1.0): + self.position = position + self.velocity = velocity + self.max_speed = max_speed + self.turn_speed = turn_speed + + def seek(self.target): + return (self.position - target.position) * self.max_speed + + def flee(self, target): + return -self.seek(target) + + def predict_intercept_pos(self, target): + return target.pos + (target.velocity * self.position.distance(target.pos) * target.turn_speed) + + def pursuit(self, target): + return self.seek(predict_intercept_pos(target)) + + def evade(self, target) + return self.flee(predict_intercept_pos(target)) + + def arrive(self, target, slowing_distance): + target_offset = target.position - self.position + distance = target_offset.len() + ramped_speed = self.max_speed * (distance / slowing_distance) + clipped_speed = min(ramped_speed, max_speed) + return (clipped_speed / distance) * target_offset + Added: vector.py =================================================================== --- vector.py (rev 0) +++ vector.py 2007-01-09 13:44:53 UTC (rev 7) @@ -0,0 +1,36 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Two dimensional vectors +# $Id$ +# $Date$ + +class Vector2D: + + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self, other): + return Vector2D(self.x + other.x, self.y + other.y) + + def __div__(self, k): + return Vector2D(self.x / k, self.y / k) + + def __invert__(self): + return Vector2D(-self.x, -self.y) + + def __mul__(self, k): + return Vector2D(self.x * k, self.y * k) + + def __sub__(self, other): + return Vector2D(self.x - other.x, self.y - other.y) + + def len(self): + # a^2 + b^2 = c^2 + return pow(pow(x, 2) + pow(y, 2), 0.5) + + def distance(self, other): + return (self - other).len() + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-01-09 22:06:14
|
Revision: 13 http://eos-game.svn.sourceforge.net/eos-game/?rev=13&view=rev Author: oberon7 Date: 2007-01-09 14:06:12 -0800 (Tue, 09 Jan 2007) Log Message: ----------- A.I. randomly chooses to either evade or pursue the player. Modified Paths: -------------- ai.py game.py player.py projectile.py steer.py vector.py Property Changed: ---------------- / Property changes on: ___________________________________________________________________ Name: svn:ignore + *.pyc Modified: ai.py =================================================================== --- ai.py 2007-01-09 20:55:13 UTC (rev 12) +++ ai.py 2007-01-09 22:06:12 UTC (rev 13) @@ -20,18 +20,11 @@ self.camera = camera self.position += Vector2D(random.randint(-200, 200), random.randint(-200, 200)) self.turning = 0 + self.steerfunc = random.choice([self.pursuit, self.evade]) def update(self): # A.I. must chose which buttons to push - if not self.turning and random.randint(0,100) == 1: - self.turning = random.randint(-1, 1) - elif random.randint(0,40) == 1: - self.turning = 0 - keystate = {} - keystate[K_LEFT] = self.turning < 0 - keystate[K_RIGHT] = self.turning > 0 - keystate[K_UP] = 1 - keystate[K_DOWN] = 0 + keystate = self.steer(self.heading, self.velocity, self.steerfunc(self.camera)) keystate[K_SPACE] = 0 # emulate being a legitimate player Modified: game.py =================================================================== --- game.py 2007-01-09 20:55:13 UTC (rev 12) +++ game.py 2007-01-09 22:06:12 UTC (rev 13) @@ -44,7 +44,7 @@ clock = pygame.time.Clock() sprites = RenderedGroup() player = Player() - ai = [AI(player) for i in xrange(random.randint(1, 5))] + ai = [AI(player) for i in xrange(random.randint(5, 11))] def handle_events(): for event in pygame.event.get(): Modified: player.py =================================================================== --- player.py 2007-01-09 20:55:13 UTC (rev 12) +++ player.py 2007-01-09 22:06:12 UTC (rev 13) @@ -24,7 +24,7 @@ def __init__(self): pygame.sprite.Sprite.__init__(self, game.sprites) - Steerable.__init__(self, Vector2D(), Vector2D(), self.max_speed, self.max_turn_rate) + Steerable.__init__(self, Vector2D(), Vector2D(), self.max_speed) self.ship_img = pygame.image.load('data/ship.png').convert(game.screen) self.rect = self.ship_img.get_rect(center=game.screen.get_rect().center) self.heading = random.randint(0, 360) Modified: projectile.py =================================================================== --- projectile.py 2007-01-09 20:55:13 UTC (rev 12) +++ projectile.py 2007-01-09 22:06:12 UTC (rev 13) @@ -45,7 +45,7 @@ def __init__(self, charge, gunmount): pygame.sprite.Sprite.__init__(self, game.sprites) - Steerable.__init__(self, gunmount.position, Vector2D.unit(gunmount.heading) * self.speed, self.speed, 0) + Steerable.__init__(self, gunmount.position, Vector2D.unit(gunmount.heading) * self.speed, self.speed) self.charge = charge self.size = max(charge * 2, 2) self.distance = 0 Modified: steer.py =================================================================== --- steer.py 2007-01-09 20:55:13 UTC (rev 12) +++ steer.py 2007-01-09 22:06:12 UTC (rev 13) @@ -6,14 +6,17 @@ # $Id$ # $Date$ +import random +from pygame.locals import * +from vector import Vector2D + class Steerable: """Craig W. Reynolds "Boids" steering behaviors.""" - def __init__(self, position, velocity, max_speed, turn_speed=1.0): + def __init__(self, position=Vector2D(), velocity=Vector2D(), max_speed=0): self.position = position self.velocity = velocity self.max_speed = max_speed - self.turn_speed = turn_speed def seek(self, target): return (self.position - target.position) * self.max_speed @@ -21,19 +24,35 @@ def flee(self, target): return -self.seek(target) - def predict_intercept_pos(self, target): - return target.pos + (target.velocity * self.position.distance(target.pos) * target.turn_speed) + def predict_intercept(self, target): + # we synthesize a transient Steerable object to + # give us a targetable point in space to aim at + return Steerable( + target.position + + (target.velocity * self.position.distance(target.position))) def pursuit(self, target): - return self.seek(predict_intercept_pos(target)) + return self.seek(self.predict_intercept(target)) def evade(self, target): - return self.flee(predict_intercept_pos(target)) + return self.flee(self.predict_intercept(target)) def arrive(self, target, slowing_distance): target_offset = target.position - self.position distance = target_offset.len() ramped_speed = self.max_speed * (distance / slowing_distance) - clipped_speed = min(ramped_speed, max_speed) - return (clipped_speed / distance) * target_offset + clipped_speed = min(ramped_speed, self.max_speed) + return target_offset * (clipped_speed / distance) + def steer(self, current_heading, current_velocity, desired_velocity): + steering_force = desired_velocity - current_velocity + deg = current_heading - steering_force.degrees() + while deg < 0: deg += 360 + deg %= 360 + return { + K_LEFT : 180 < deg < 360 and 1 or 0, + K_RIGHT : 0 < deg <= 180 and 1 or 0, + K_UP : (deg < 90 or deg > 270) and 1 or 0, + K_DOWN : 90 < deg < 270 and 1 or 0, + } + Modified: vector.py =================================================================== --- vector.py 2007-01-09 20:55:13 UTC (rev 12) +++ vector.py 2007-01-09 22:06:12 UTC (rev 13) @@ -34,6 +34,9 @@ return self * (k / self.len()) return self + def degrees(self): + return math.degrees(math.atan2(self.x, self.y)) + def distance(self, other): return (self - other).len() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-09 22:56:14
|
Revision: 14 http://eos-game.svn.sourceforge.net/eos-game/?rev=14&view=rev Author: cduncan Date: 2007-01-09 14:56:09 -0800 (Tue, 09 Jan 2007) Log Message: ----------- Implement side maneuvering thrusters in addition to back. Arrow keys are now for thrusters and 'z' and 'x' rotate. Thrusters have 75% of fw thrust, which seems a bit high to me in theory but lower values were too subtle in effect. Modified Paths: -------------- ai.py player.py steer.py Modified: ai.py =================================================================== --- ai.py 2007-01-09 22:06:12 UTC (rev 13) +++ ai.py 2007-01-09 22:56:09 UTC (rev 14) @@ -13,7 +13,7 @@ class AI(Player): - max_speed = 4 + max_speed = 5 def __init__(self, camera): Player.__init__(self) Modified: player.py =================================================================== --- player.py 2007-01-09 22:06:12 UTC (rev 13) +++ player.py 2007-01-09 22:56:09 UTC (rev 14) @@ -17,7 +17,7 @@ class Player(pygame.sprite.Sprite, Steerable): fw_thrust = 0.1 - bw_thrust = fw_thrust * 0.33 + maneuver_thrust = fw_thrust * 0.75 max_speed = 6 max_turn_rate = 2 turn_accel = 0.12 @@ -43,7 +43,7 @@ # turning last_heading = self.heading - turn_dir = keystate[K_LEFT] - keystate[K_RIGHT] + turn_dir = keystate[K_z] - keystate[K_x] if turn_dir: self.turn += turn_dir * self.turn_accel self.turn = min(max(self.turn, -self.max_turn_rate), self.max_turn_rate) @@ -59,7 +59,11 @@ if keystate[K_UP]: self.velocity += Vector2D.unit(self.heading) * self.fw_thrust if keystate[K_DOWN]: - self.velocity -= Vector2D.unit(self.heading) * self.fw_thrust + self.velocity -= Vector2D.unit(self.heading) * self.maneuver_thrust + if keystate[K_LEFT]: + self.velocity += Vector2D.unit(self.heading + 90) * self.maneuver_thrust + if keystate[K_RIGHT]: + self.velocity += Vector2D.unit(self.heading - 90) * self.maneuver_thrust self.velocity = self.velocity.clamp(self.max_speed) self.position += self.velocity Modified: steer.py =================================================================== --- steer.py 2007-01-09 22:06:12 UTC (rev 13) +++ steer.py 2007-01-09 22:56:09 UTC (rev 14) @@ -50,8 +50,10 @@ while deg < 0: deg += 360 deg %= 360 return { - K_LEFT : 180 < deg < 360 and 1 or 0, - K_RIGHT : 0 < deg <= 180 and 1 or 0, + K_LEFT: 0, + K_RIGHT: 0, + K_z : 180 < deg < 360 and 1 or 0, + K_x : 0 < deg <= 180 and 1 or 0, K_UP : (deg < 90 or deg > 270) and 1 or 0, K_DOWN : 90 < deg < 270 and 1 or 0, } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-10 06:44:45
|
Revision: 16 http://eos-game.svn.sourceforge.net/eos-game/?rev=16&view=rev Author: cduncan Date: 2007-01-09 22:44:43 -0800 (Tue, 09 Jan 2007) Log Message: ----------- Refactor/optimize 2DVector - len(), radians() methods are now length and radians properties Which automatically cache their values - Optimize length: x**2 is much faster than pow(x,2) and sqrt(x) is marginally faster than pow(x, 0.5) as measured using timeit. Expression is more readable also Modified Paths: -------------- projectile.py steer.py vector.py Modified: projectile.py =================================================================== --- projectile.py 2007-01-10 06:17:50 UTC (rev 15) +++ projectile.py 2007-01-10 06:44:43 UTC (rev 16) @@ -65,7 +65,7 @@ def update(self): self.rect[0][0] += self.velocity.x self.rect[0][1] += self.velocity.y - self.distance += self.velocity.len() + self.distance += self.velocity.length if self.range - self.distance < self.speed * 10: self.colormax = int(self.colormax * 0.85) if self.distance >= self.range: Modified: steer.py =================================================================== --- steer.py 2007-01-10 06:17:50 UTC (rev 15) +++ steer.py 2007-01-10 06:44:43 UTC (rev 16) @@ -8,7 +8,7 @@ import math from pygame.locals import * -from vector import Vector2D, north, south, east, west +from vector import Vector2D, north, south, east, west, fullcircle class Steerable: """Craig W. Reynolds "Boids" steering behaviors.""" @@ -39,14 +39,14 @@ def arrive(self, target, slowing_distance): target_offset = target.position - self.position - distance = target_offset.len() + distance = target_offset.length ramped_speed = self.max_speed * (distance / slowing_distance) clipped_speed = min(ramped_speed, self.max_speed) return target_offset * (clipped_speed / distance) def steer(self, current_heading, current_velocity, desired_velocity): steering_force = desired_velocity - current_velocity - rad = (current_heading - steering_force.radians()) % (math.pi * 2) + rad = (current_heading - steering_force.radians) % fullcircle return { K_a : 0, K_d : 0, Modified: vector.py =================================================================== --- vector.py 2007-01-10 06:17:50 UTC (rev 15) +++ vector.py 2007-01-10 06:44:43 UTC (rev 16) @@ -7,6 +7,7 @@ # $Date$ import math +from math import sqrt # number of cached unit vectors unit_vectors = 360 @@ -15,10 +16,6 @@ class Vector2D: - # Cached values for speeding calculations - _radians = None - _len = None - def __init__(self, x=0, y=0): self.x = x self.y = y @@ -39,26 +36,30 @@ return Vector2D(self.x - other.x, self.y - other.y) def clamp(self, k): - if self.len() > k: - return self * (k / self.len()) + if self.length > k: + return self * (k / self.length) return self + @property def radians(self): - return self._radians or math.atan2(self.x, self.y) + r = self.radians = math.atan2(self.x, self.y) + return r def distance(self, other): - return (self - other).len() + return (self - other).length - def len(self): + @property + def length(self): # a^2 + b^2 = c^2 - return self._len or pow(pow(self.x, 2) + pow(self.y, 2), 0.5) + L = self.length = sqrt(self.x**2 + self.y**2) + return L def dotprod(self, other): """Compute the dot product of two vectors returning the scalar value """ - theta = self.radians() - other.radians() - return self.len() * other.len() * math.cos(theta) + theta = self.radians - other.radians + return self.length * other.length * math.cos(theta) @classmethod def unit(cls, radians): @@ -70,8 +71,8 @@ for d in range(unit_vectors): r = d * unit_vector_res uv = Vector2D(math.cos(r), math.sin(r)) - uv._radians = r - uv._len = 1 + uv.radians = r + uv.length = 1 _heading_vec[d] = uv # common heading angles This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-10 09:23:37
|
Revision: 20 http://eos-game.svn.sourceforge.net/eos-game/?rev=20&view=rev Author: cduncan Date: 2007-01-10 01:23:35 -0800 (Wed, 10 Jan 2007) Log Message: ----------- Greatly reduce number of transient objects created each frame. Modified Paths: -------------- ai.py player.py steer.py vector.py Modified: ai.py =================================================================== --- ai.py 2007-01-10 08:12:54 UTC (rev 19) +++ ai.py 2007-01-10 09:23:35 UTC (rev 20) @@ -31,9 +31,6 @@ Player.update(self, keystate) # place self relative to the camera - where = Vector2D(self.camera.rect.centerx, self.camera.rect.centery) - where -= self.camera.position # change where in-place - where += self.position # change where in-place - self.rect.centerx = where.x - self.rect.centery = where.y - + self.rect.centerx = self.camera.rect.centerx - self.camera.position.x + self.position.x + self.rect.centery = self.camera.rect.centery - self.camera.position.y + self.position.y + Modified: player.py =================================================================== --- player.py 2007-01-10 08:12:54 UTC (rev 19) +++ player.py 2007-01-10 09:23:35 UTC (rev 20) @@ -31,9 +31,9 @@ self.heading = random.random() * fullcircle self.turn = 0.0 self.weapon = FullereneCannon(self) - self.update_ship() + self.update_ship_img() - def update_ship(self): + def update_ship_img(self): self.image = pygame.transform.rotate( self.ship_img, 270 - math.degrees(self.heading)) self.rect = self.image.get_rect(center=self.rect.center) @@ -54,8 +54,9 @@ elif self.turn: self.turn = min(self.turn + self.turn_accel, 0) self.heading += self.turn - if self.heading != last_heading: - self.update_ship() + turned = self.heading != last_heading + if turned: + self.update_ship_img() # thrust if keystate[K_UP]: @@ -63,7 +64,8 @@ self.max_speed = Player.max_speed * 1.6 else: self.max_speed = Player.max_speed - self.velocity += Vector2D.unit(self.heading) * self.fw_thrust + if turned or self.velocity.length < self.max_speed: + self.velocity += Vector2D.unit(self.heading) * self.fw_thrust if keystate[K_w]: self.velocity += Vector2D.unit(self.heading) * self.maneuver_thrust @@ -73,7 +75,7 @@ self.velocity += Vector2D.unit(self.heading - rightangle) * self.maneuver_thrust if keystate[K_d]: self.velocity += Vector2D.unit(self.heading + rightangle) * self.maneuver_thrust - self.velocity = self.velocity.clamp(self.max_speed) + self.velocity.clamp_in(self.max_speed) self.position += self.velocity self.weapon.update(keystate[K_SPACE]) Modified: steer.py =================================================================== --- steer.py 2007-01-10 08:12:54 UTC (rev 19) +++ steer.py 2007-01-10 09:23:35 UTC (rev 20) @@ -19,30 +19,37 @@ self.max_speed = max_speed def seek(self, target): - return (self.position - target.position) * self.max_speed + v = self.predict_intercept(target) + v -= self.position + v *= -self.max_speed + return v def flee(self, target): - return -self.seek(target) + v = self.predict_intercept(target) + v -= self.position + v *= self.max_speed + return v def predict_intercept(self, target): # we synthesize a transient Steerable object to # give us a targetable point in space to aim at - return Steerable( - target.position - + (target.velocity * self.position.distance(target.position))) + target_waypoint = target.velocity * self.position.distance(target.position) + target_waypoint += target.position + return target_waypoint def pursuit(self, target): - return self.seek(self.predict_intercept(target)) + return self.seek(target) def evade(self, target): - return self.flee(self.predict_intercept(target)) + return self.flee(target) def arrive(self, target, slowing_distance): target_offset = target.position - self.position distance = target_offset.length ramped_speed = self.max_speed * (distance / slowing_distance) clipped_speed = min(ramped_speed, self.max_speed) - return target_offset * (clipped_speed / distance) + target_offset *= (clipped_speed / distance) + return target_offset def steer(self, current_heading, current_velocity, desired_velocity): steering_force = desired_velocity - current_velocity Modified: vector.py =================================================================== --- vector.py 2007-01-10 08:12:54 UTC (rev 19) +++ vector.py 2007-01-10 09:23:35 UTC (rev 20) @@ -143,6 +143,11 @@ if self.length > k: return self * (k / self.length) return self + + def clamp_in(self, k): + """Clamp vector to length in place""" + if self.length > k: + self *= (k / self.length) @property def radians(self): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-01-15 09:12:19
|
Revision: 31 http://eos-game.svn.sourceforge.net/eos-game/?rev=31&view=rev Author: oberon7 Date: 2007-01-15 01:12:17 -0800 (Mon, 15 Jan 2007) Log Message: ----------- Neutered AI of its current broken-ness. Also changed the semantics of RoundBody.position and RoundBody.velocity vectors to represent the vectos in the ODE universe, and not within Pygame pixel land. Modified Paths: -------------- ai.py body.py game.py player.py projectile.py stars.py Removed Paths: ------------- steer.py Modified: ai.py =================================================================== --- ai.py 2007-01-15 09:03:15 UTC (rev 30) +++ ai.py 2007-01-15 09:12:17 UTC (rev 31) @@ -6,31 +6,45 @@ # $Id$ # $Date$ -import random from pygame.locals import * from player import Player -from vector import Vector2D +from vector import halfcircle, fullcircle class AI(Player): - - max_speed = 5 - def __init__(self, camera): + def __init__(self, target): Player.__init__(self) - self.camera = camera - self.position += random.randint(-200, 200), random.randint(-200, 200) - self.turning = 0 - self.steerfunc = random.choice([self.pursuit, self.evade]) + self.target = target + self.steerfunc = self.seek + def seek(self, target): + return (self.position - target.position).normal * self.max_speed + + def steer(self, desired_velocity): + delta = self.heading - desired_velocity.radians + print self.heading + print desired_velocity.radians + print + return { + K_a : 0, + K_d : 0, + K_w : 0, + K_s : 0, + K_RIGHT : 0, + K_LEFT : 0, + K_UP : 0, + K_DOWN : 0, + } + def update(self): - # A.I. must chose which buttons to push - keystate = self.steer(self.heading, self.velocity, self.steerfunc(self.camera)) + keystate = {} + + # steering + keystate.update(self.steer(self.steerfunc(self.target))) + + # shooting keystate[K_SPACE] = 0 # emulate being a legitimate player Player.update(self, keystate) - # place self relative to the camera - #self.rect.centerx = self.camera.rect.centerx - self.camera.position.x + self.position.x - #self.rect.centery = self.camera.rect.centery - self.camera.position.y + self.position.y - Modified: body.py =================================================================== --- body.py 2007-01-15 09:03:15 UTC (rev 30) +++ body.py 2007-01-15 09:12:17 UTC (rev 31) @@ -34,23 +34,29 @@ self.body.setPosition((position[0], position[1], 0)) if velocity is not None: self.body.setLinearVel(velocity[0], velocity[1]) + # mapping the ODE universe + self.position = Vector2D() + self.velocity = Vector2D() # on screen size and positioning self.rect = pygame.Rect([position[0], position[1], 0, 0]) - self.position = Vector2D() - self.velocity = Vector2D() + + def _ode_sync(self): + # position + x, y, ignore = self.body.getPosition() + self.position.x = x + self.position.y = y + # velocity + x, y, ignore = self.body.getLinearVel() + self.velocity.x = x + self.velocity.y = y def update(self): """Calculate onscreen position and size""" + self._ode_sync() + # place self relative to the camera at proper zoom level camera = game.camera - # place self relative to the camera at proper zoom level - x, y, ignore = self.body.getPosition() - self.position.x = x * camera.zoom - self.position.y = y * camera.zoom - self.rect.centerx = camera.rect.centerx - camera.position.x + self.position.x - self.rect.centery = camera.rect.centery - camera.position.y + self.position.y - x, y, ignore = self.body.getLinearVel() - self.velocity.x = x * camera.zoom - self.velocity.y = y * camera.zoom + self.rect.centerx = camera.rect.centerx + ((self.position.x - camera.position.x) * camera.zoom) + self.rect.centery = camera.rect.centery + ((self.position.y - camera.position.y) * camera.zoom) def push(self, force): """Apply the force vector to the body for this frame""" Modified: game.py =================================================================== --- game.py 2007-01-15 09:03:15 UTC (rev 30) +++ game.py 2007-01-15 09:12:17 UTC (rev 31) @@ -57,7 +57,8 @@ camera = player = Player() camera.rect.center = screen.get_rect().center camera.zoom = 1 - ai = [AI(player) for i in xrange(random.randint(3,9))] + #ai = [AI(player) for i in xrange(random.randint(3,9))] + ai = AI(player) def handle_events(): for event in pygame.event.get(): Modified: player.py =================================================================== --- player.py 2007-01-15 09:03:15 UTC (rev 30) +++ player.py 2007-01-15 09:12:17 UTC (rev 31) @@ -13,11 +13,10 @@ import game from projectile import FullereneCannon from body import RoundBody -from steer import Steerable from vector import Vector2D, fullcircle, rightangle from media import RotatedImage -class Player(RoundBody, Steerable): +class Player(RoundBody): mass = 2 size = 25 @@ -32,7 +31,6 @@ def __init__(self): RoundBody.__init__(self, (random.randint(-200,200),random.randint(-200,200))) - Steerable.__init__(self, Vector2D(), Vector2D(), self.max_speed) self.ship_img = RotatedImage('ship.png') self.heading = random.random() * fullcircle self.turn = 0.0 @@ -60,6 +58,7 @@ elif self.turn: self.turn = min(self.turn + self.turn_accel, 0) self.heading += self.turn + self.heading %= fullcircle turned = self.heading != last_heading if turned: self.update_ship_img() @@ -82,12 +81,10 @@ if keystate[K_d]: self.thrust += Vector2D.unit(self.heading + rightangle) * self.maneuver_thrust if self.thrust: - x, y, ignore = self.body.getLinearVel() - velocity = Vector2D(x, y) - if velocity.length: + if self.velocity.length: # Bleed off thrust at speed - coincidence = self.thrust.normal.dotprod(velocity.normal) - self.thrust *= (1 - coincidence) * self.max_speed / velocity.length + coincidence = self.thrust.normal.dotprod(self.velocity.normal) + self.thrust *= (1 - coincidence) * self.max_speed / self.velocity.length self.thrust.clamp_in(self.fw_thrust) self.push(self.thrust) Modified: projectile.py =================================================================== --- projectile.py 2007-01-15 09:03:15 UTC (rev 30) +++ projectile.py 2007-01-15 09:12:17 UTC (rev 31) @@ -11,7 +11,6 @@ import pygame from pygame.locals import * import game -from steer import Steerable from vector import Vector2D, fullcircle @@ -37,7 +36,7 @@ self.charge = 0 -class Fullerene(pygame.sprite.Sprite, Steerable): +class Fullerene(pygame.sprite.Sprite): speed = 14.0 range = 450 @@ -47,8 +46,6 @@ def __init__(self, charge, gunmount): pygame.sprite.Sprite.__init__(self, game.sprites) - Steerable.__init__(self, gunmount.position, - Vector2D.unit(gunmount.heading) * self.speed, self.speed) self.charge = charge self.size = max(charge * 2, 2) self.distance = 0 @@ -58,13 +55,14 @@ self.rot = 0 self.step = range(self.particles) self.colormax = 255 + self.velocity = Vector2D.unit(gunmount.heading) * self.speed def clear(self): game.screen.fill((0,0,0), self.rect) def update(self): - self.rect[0][0] += self.velocity.x - self.rect[0][1] += self.velocity.y + self.rect[0][0] += self.velocity.x * game.camera.zoom + self.rect[0][1] += self.velocity.y * game.camera.zoom self.distance += self.velocity.length if self.range - self.distance < self.speed * 10: self.colormax = int(self.colormax * 0.85) Modified: stars.py =================================================================== --- stars.py 2007-01-15 09:03:15 UTC (rev 30) +++ stars.py 2007-01-15 09:12:17 UTC (rev 31) @@ -80,6 +80,6 @@ star_rect[0] = randint(leftbound, rightbound) def update(self): - vx = -game.camera.velocity.x / game.fps - vy = -game.camera.velocity.y / game.fps + vx = - game.camera.zoom * game.camera.velocity.x / game.fps + vy = - game.camera.zoom * game.camera.velocity.y / game.fps self.move((vx, vy)) Deleted: steer.py =================================================================== --- steer.py 2007-01-15 09:03:15 UTC (rev 30) +++ steer.py 2007-01-15 09:12:17 UTC (rev 31) @@ -1,67 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (c) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Steering behaviors -# $Id$ -# $Date$ - -import math -from pygame.locals import * -from vector import Vector2D, north, south, east, west, fullcircle - -class Steerable: - """Craig W. Reynolds "Boids" steering behaviors.""" - - def __init__(self, position=Vector2D(), velocity=Vector2D(), max_speed=0): - self.position = position - self.velocity = velocity - self.max_speed = max_speed - - def seek(self, target): - v = self.predict_intercept(target) - v -= self.position - v *= -self.max_speed - return v - - def flee(self, target): - v = self.predict_intercept(target) - v -= self.position - v *= self.max_speed - return v - - def predict_intercept(self, target): - # we synthesize a transient Steerable object to - # give us a targetable point in space to aim at - target_waypoint = target.velocity * self.position.distance(target.position) - target_waypoint += target.position - return target_waypoint - - def pursuit(self, target): - return self.seek(target) - - def evade(self, target): - return self.flee(target) - - def arrive(self, target, slowing_distance): - target_offset = target.position - self.position - distance = target_offset.length - ramped_speed = self.max_speed * (distance / slowing_distance) - clipped_speed = min(ramped_speed, self.max_speed) - target_offset *= (clipped_speed / distance) - return target_offset - - def steer(self, current_heading, current_velocity, desired_velocity): - steering_force = desired_velocity - current_velocity - rad = (current_heading - steering_force.radians) % fullcircle - return { - K_a : 0, - K_d : 0, - K_w : 0, - K_s : 0, - K_RIGHT : south < rad < (south + math.pi), - K_LEFT : north < rad <= south, - K_UP : rad < east or rad > west, - K_DOWN : east < rad < west, - } - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-15 09:36:16
|
Revision: 32 http://eos-game.svn.sourceforge.net/eos-game/?rev=32&view=rev Author: cduncan Date: 2007-01-15 01:36:14 -0800 (Mon, 15 Jan 2007) Log Message: ----------- - Projectiles are now ode bodies - Fix bug with setting initial velocity for RoundBody - Full screen baby, wooo! Modified Paths: -------------- body.py game.py projectile.py Modified: body.py =================================================================== --- body.py 2007-01-15 09:12:17 UTC (rev 31) +++ body.py 2007-01-15 09:36:14 UTC (rev 32) @@ -33,7 +33,7 @@ self.body.setMass(mass) self.body.setPosition((position[0], position[1], 0)) if velocity is not None: - self.body.setLinearVel(velocity[0], velocity[1]) + self.body.setLinearVel((velocity[0], velocity[1], 0)) # mapping the ODE universe self.position = Vector2D() self.velocity = Vector2D() Modified: game.py =================================================================== --- game.py 2007-01-15 09:12:17 UTC (rev 31) +++ game.py 2007-01-15 09:36:14 UTC (rev 32) @@ -38,7 +38,7 @@ pygame.init() # Get the available display modes and use the best one modes = pygame.display.list_modes() - screen = pygame.display.set_mode((800,800)) #modes[0], FULLSCREEN) + screen = pygame.display.set_mode(modes[0], FULLSCREEN) pygame.display.set_caption('Eos') background = pygame.Surface(screen.get_size()) background.fill((0,0,0)) Modified: projectile.py =================================================================== --- projectile.py 2007-01-15 09:12:17 UTC (rev 31) +++ projectile.py 2007-01-15 09:36:14 UTC (rev 32) @@ -11,6 +11,7 @@ import pygame from pygame.locals import * import game +from body import RoundBody from vector import Vector2D, fullcircle @@ -36,22 +37,27 @@ self.charge = 0 -class Fullerene(pygame.sprite.Sprite): +class Fullerene(RoundBody): - speed = 14.0 + speed = 450 range = 450 - color = (255,220,20) particles = 4 - spin = math.radians(8) + rpm = 80 def __init__(self, charge, gunmount): - pygame.sprite.Sprite.__init__(self, game.sprites) + self.mass = charge + self.radius = max(charge, 1) + RoundBody.__init__( + self, gunmount.body.getPosition(), + Vector2D.unit(gunmount.heading) * self.speed + gunmount.body.getLinearVel()) + #Steerable.__init__(self, gunmount.position, + # Vector2D.unit(gunmount.heading) * self.speed, self.speed) self.charge = charge - self.size = max(charge * 2, 2) self.distance = 0 - self.partsize = max(self.charge * 1.25, 2) - self.rect = (list(gunmount.rect.center), (self.partsize, self.partsize)) + self.distance_per_frame = self.speed * game.dt + self.partsize = max(charge * 1.25, 2) self.partangle = fullcircle/self.particles + self.rot_per_frame = self.rpm * fullcircle / (game.fps * 60) self.rot = 0 self.step = range(self.particles) self.colormax = 255 @@ -61,27 +67,29 @@ game.screen.fill((0,0,0), self.rect) def update(self): - self.rect[0][0] += self.velocity.x * game.camera.zoom - self.rect[0][1] += self.velocity.y * game.camera.zoom - self.distance += self.velocity.length - if self.range - self.distance < self.speed * 10: + RoundBody.update(self) + self.distance += self.distance_per_frame + if self.range - self.distance < self.distance_per_frame * 10: self.colormax = int(self.colormax * 0.85) if self.distance >= self.range: self.kill() def draw(self, surface): + partsize = self.partsize * game.camera.zoom + radius = self.charge * game.camera.zoom rects = [self.rect] bg = randint(0, self.colormax) - surface.fill((self.colormax, bg, self.colormax-bg), self.rect) + surface.fill((self.colormax, bg, self.colormax-bg), + (self.rect.center, (partsize, partsize))) for i in self.step: direction = Vector2D.unit(self.rot) - part = (self.rect[0][0] + direction.x * self.charge, - self.rect[0][1] + direction.y * self.charge, - self.partsize, self.partsize) + part = (self.rect.centerx + direction.x * radius, + self.rect.centery + direction.y * radius, + partsize, partsize) rects.append(part) bg = randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg) , part) self.rot += self.partangle - self.rot += self.spin + self.rot += self.rot_per_frame return Rect(rects[0]).unionall(rects) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-01-15 09:42:33
|
Revision: 34 http://eos-game.svn.sourceforge.net/eos-game/?rev=34&view=rev Author: oberon7 Date: 2007-01-15 01:42:32 -0800 (Mon, 15 Jan 2007) Log Message: ----------- Added a few oddball Vector2D unit tests. Modified Paths: -------------- ai.py vector.py Modified: ai.py =================================================================== --- ai.py 2007-01-15 09:37:05 UTC (rev 33) +++ ai.py 2007-01-15 09:42:32 UTC (rev 34) @@ -24,6 +24,7 @@ delta = self.heading - desired_velocity.radians print self.heading print desired_velocity.radians + print desired_velocity.normal print return { K_a : 0, Modified: vector.py =================================================================== --- vector.py 2007-01-15 09:37:05 UTC (rev 33) +++ vector.py 2007-01-15 09:42:32 UTC (rev 34) @@ -14,7 +14,20 @@ unit_vector_res = max_radian / unit_vectors class Vector2D(object): + """Two dimensional vector + >>> Vector2D(3, 4).length + 5.0 + >>> Vector2D(100, 0).normal.x + 1.0 + >>> Vector2D(100, 0).normal.y + 0.0 + >>> Vector2D(0, -42).normal.x + 0.0 + >>> Vector2D(0, -42).normal.y + -1.0 + """ + def __init__(self, x=0, y=0): """Create a 2 dimensional vector This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-01-15 18:35:07
|
Revision: 38 http://eos-game.svn.sourceforge.net/eos-game/?rev=38&view=rev Author: oberon7 Date: 2007-01-15 10:35:04 -0800 (Mon, 15 Jan 2007) Log Message: ----------- Fixed AI steering by cheating how it turns. Modified Paths: -------------- ai.py body.py game.py player.py Modified: ai.py =================================================================== --- ai.py 2007-01-15 10:14:33 UTC (rev 37) +++ ai.py 2007-01-15 18:35:04 UTC (rev 38) @@ -6,6 +6,7 @@ # $Id$ # $Date$ +import random from pygame.locals import * from player import Player from vector import halfcircle, fullcircle @@ -15,12 +16,26 @@ def __init__(self, target): Player.__init__(self) self.target = target - self.steerfunc = self.seek + self.steerfunc = self.pursue - def seek(self, target): - return (self.position - target.position).normal * self.max_speed + def seek_position(self, target_position): + return (target_position - self.position).normal * self.max_speed + def seek(self): + return self.seek_position(self.target.position) + + def _predict_position(self, target): + if target.velocity.normal is not None: + T = self.position.distance(target.position) / self.max_speed + return target.position + (target.velocity * T) + else: + return target.position + + def pursue(self): + return self.seek_position(self._predict_position(self.target)) + def steer(self, desired_velocity): + self.heading = (desired_velocity - self.velocity).radians return { K_a : 0, K_d : 0, @@ -28,15 +43,15 @@ K_s : 0, K_RIGHT : 0, K_LEFT : 0, - K_UP : 0, - K_DOWN : 0, + K_UP : 1, + K_DOWN : desired_velocity.length < self.velocity.length, } def update(self): keystate = {} # steering - keystate.update(self.steer(self.steerfunc(self.target))) + keystate.update(self.steer(self.steerfunc())) # shooting keystate[K_SPACE] = 0 Modified: body.py =================================================================== --- body.py 2007-01-15 10:14:33 UTC (rev 37) +++ body.py 2007-01-15 18:35:04 UTC (rev 38) @@ -37,6 +37,7 @@ # mapping the ODE universe self.position = Vector2D() self.velocity = Vector2D() + self._ode_sync() # on screen size and positioning self.rect = pygame.Rect([position[0], position[1], 0, 0]) Modified: game.py =================================================================== --- game.py 2007-01-15 10:14:33 UTC (rev 37) +++ game.py 2007-01-15 18:35:04 UTC (rev 38) @@ -57,8 +57,7 @@ camera = player = Player() camera.rect.center = screen.get_rect().center camera.zoom = 1 - #ai = [AI(player) for i in xrange(random.randint(3,9))] - ai = AI(player) + ai = [AI(player) for i in xrange(random.randint(3,9))] def handle_events(): for event in pygame.event.get(): Modified: player.py =================================================================== --- player.py 2007-01-15 10:14:33 UTC (rev 37) +++ player.py 2007-01-15 18:35:04 UTC (rev 38) @@ -48,7 +48,6 @@ keystate = pygame.key.get_pressed() # turning - last_heading = self.heading turn_dir = keystate[K_RIGHT] - keystate[K_LEFT] if turn_dir: self.turn += turn_dir * self.turn_accel @@ -59,9 +58,7 @@ self.turn = min(self.turn + self.turn_accel, 0) self.heading += self.turn self.heading %= fullcircle - turned = self.heading != last_heading - if turned: - self.update_ship_img() + self.update_ship_img() # thrust if keystate[K_UP]: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-15 19:56:36
|
Revision: 42 http://eos-game.svn.sourceforge.net/eos-game/?rev=42&view=rev Author: cduncan Date: 2007-01-15 11:56:28 -0800 (Mon, 15 Jan 2007) Log Message: ----------- - Implement simple speed limit that bleed off speed slowly if you go over max_speed. Since max_speed is variable this makes letting off boost fairly smooth. Ideally letting off boost would not slow you down in your current direction, but that's pretty complex to get right. This is good enough for now. - rename Vector2D.clamp_in to Vector2D.clamp_ip like pygame api Modified Paths: -------------- player.py vector.py Modified: player.py =================================================================== --- player.py 2007-01-15 19:41:05 UTC (rev 41) +++ player.py 2007-01-15 19:56:28 UTC (rev 42) @@ -79,6 +79,12 @@ self.thrust += Vector2D.unit(self.heading + rightangle) * self.maneuver_thrust if self.thrust: self.push(self.thrust) + if self.velocity.length > self.max_speed: + # If we are overspeed, bleed off a little + # rather abruptly clamp it, otherwise we'd have + # an instantaneous deceleration when we let off boost. + self.velocity *= 0.98 + self.body.setLinearVel(self.velocity) self.weapon.update(keystate[K_SPACE]) Modified: vector.py =================================================================== --- vector.py 2007-01-15 19:41:05 UTC (rev 41) +++ vector.py 2007-01-15 19:56:28 UTC (rev 42) @@ -177,7 +177,7 @@ return self * (k / self.length) return self - def clamp_in(self, k): + def clamp_ip(self, k): """Clamp vector to length in place""" if self.length > k: self *= (k / self.length) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-19 10:30:25
|
Revision: 53 http://eos-game.svn.sourceforge.net/eos-game/?rev=53&view=rev Author: cduncan Date: 2007-01-19 02:30:21 -0800 (Fri, 19 Jan 2007) Log Message: ----------- - AI can now actually track down targets or run away - AI moves out of the way of adjacent ships to avoid stacking - Disable the collision geom when the sprite is killed to avoid "ghost" collisions after a kill - Add copy() and equality methods to vector - Print the average frame rate on exit for a rough perf check - Tweak fullerene charge/hold, shield opacity TODO: fixup AI tests Modified Paths: -------------- ai.py body.py eos.py game.py player.py projectile.py vector.py Modified: ai.py =================================================================== --- ai.py 2007-01-17 08:57:40 UTC (rev 52) +++ ai.py 2007-01-19 10:30:21 UTC (rev 53) @@ -11,15 +11,18 @@ import game import body from player import Player -from vector import Vector2D +from vector import Vector2D, diagonal, halfcircle, rightangle, fullcircle class AI(Player): category = body.foe + collides_with = body.friend | body.foe + max_speed = 1000 def __init__(self, target): Player.__init__(self) self.target = target + self.close_vessels = [] self.steerfunc = random.choice([self.seek, self.flee, self.pursue, self.evade]) def seek_position(self, target_position): @@ -35,9 +38,9 @@ >>> vel.y 0.0 """ - return (target_position - self.position).normal * self.max_speed + return (target_position - self.position).clamp(self.max_speed) - def seek(self): + def seek(self, standoff=200): """Return desired velocity towards our target >>> game.init() @@ -51,7 +54,18 @@ >>> vel.y 33.0 """ - return self.seek_position(self.target.position) + target_position = self.target.position + target_velocity = self.target.velocity + approach = (target_position + target_velocity) - (self.position + self.velocity) + new_heading = approach.radians #(target_position - self.position).radians + if not standoff or approach.length > standoff: + if self.velocity.length and approach.length / self.velocity.length < game.avg_fps: + return new_heading, approach.clamp(self.velocity.length) + else: + return new_heading, approach.clamp(self.max_speed) + else: + # Close in, match speed with the target + return new_heading, self.target.velocity.copy() def flee(self): """Return desired velocity away from our target @@ -67,8 +81,10 @@ >>> vel.y -33.0 """ - return -self.seek() + heading, velocity = self.seek() + return (heading + halfcircle) % fullcircle, -velocity + def predict_intercept(self, target): """Return predicted position of target at the time when we should intercept them @@ -86,7 +102,7 @@ >>> pos.y 0.0 """ - if target.velocity.normal is not None: + if target.velocity: T = self.position.distance(target.position) / self.max_speed return target.position + (target.velocity * T) else: @@ -109,11 +125,14 @@ >>> vel.y 0.0 """ - return self.seek_position(self.predict_intercept(self.target)) + heading = (self.target.position - self.position).radians + return heading, self.seek_position(self.predict_intercept(self.target)) def evade(self): """Return desired velocity away from where we predict - our target to be + our target to be. Under evasion, we still turn toward + the target, under the assumption that we will resume + a pursuit or seek afterward. >>> game.init() >>> ai = AI(None) @@ -128,26 +147,64 @@ >>> vel.y 42.0 """ - return -self.pursue() + heading, velocity = self.pursue() + return heading, -velocity + + def avoid_vessels(self): + """Return a vector away from other nearby vessels to avoid stacking up + """ + center = Vector2D() + if self.close_vessels: + # Compute the center vector amongst our vessels and avoid it + for vessel in self.close_vessels: + center += vessel.position + center /= len(self.close_vessels) + return (self.position - center).normal * self.velocity.length + else: + return center - def steer(self, desired_velocity): - self.heading = (desired_velocity - self.velocity).radians + def steer(self, desired_heading, desired_velocity): + heading_diff = desired_heading - self.heading + if heading_diff > halfcircle: + heading_diff -= fullcircle + elif heading_diff < -halfcircle: + heading_diff += fullcircle keymap = [0] * 1000 - keymap[K_UP] = 1 - keymap[K_DOWN] = desired_velocity.length < self.velocity.length + + if heading_diff > self.turn * abs(self.turn) / self.turn_accel: + keymap[K_RIGHT] = 1 + elif heading_diff < self.turn * abs(self.turn) / self.turn_accel: + keymap[K_LEFT] = 1 + # Only apply thrust if our velocity needs adjustment and we are + # not turning at the full rate. The latter prevents "orbiting" + # targets and make the ship movement look more "natural" + # overshoots tend to correct themselves better this way + if self.velocity != desired_velocity and abs(self.turn) < self.max_turn_rate: + thrust_dir = (desired_velocity - self.velocity).radians - self.heading + keymap[K_UP] = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal + keymap[K_d] = halfcircle > thrust_dir + diagonal > rightangle + keymap[K_s] = halfcircle + rightangle > thrust_dir + diagonal > halfcircle + keymap[K_a] = fullcircle > thrust_dir + diagonal > halfcircle + rightangle return keymap def update(self): # steering - keystate = self.steer(self.steerfunc()) + desired_heading, desired_velocity = self.steerfunc() + desired_velocity += self.avoid_vessels() + keystate = self.steer(desired_heading, desired_velocity) + self.close_vessels = [] # Reset for next frame # shooting keystate[K_SPACE] = 0 # emulate being a legitimate player Player.update(self, keystate) + + def collide(self, other, contacts): + if isinstance(other, AI): + # Keep track of other vessels were are stacked on + self.close_vessels.append(other) - if __name__ == '__main__': """Run tests if executed directly""" import sys, doctest Modified: body.py =================================================================== --- body.py 2007-01-17 08:57:40 UTC (rev 52) +++ body.py 2007-01-19 10:30:21 UTC (rev 53) @@ -32,6 +32,9 @@ category = nothing # category for collision interaction collides_with = nothing # category bitmap this body collides with + body = None # ode dynamics body + geom = None # ode collision geom + def __init__(self, position, velocity=None): """Create the body in the given position vector which can be either a Vector2D or a tuple. An optional @@ -49,11 +52,11 @@ self.body.setLinearVel((velocity[0], velocity[1], 0)) if self.category or self.collides_with: # Add a geom for this body to the collision space - geom = ode.GeomSphere(game.collision_space, self.radius) - geom.setCategoryBits(self.category) - geom.setCollideBits(self.collides_with) - geom.setBody(self.body) # Move with the ode body - geom.parent = self # attach ourself for callback purposes + self.geom = ode.GeomSphere(game.collision_space, self.radius) + self.geom.setCategoryBits(self.category) + self.geom.setCollideBits(self.collides_with) + self.geom.setBody(self.body) # Move with the ode body + self.geom.parent = self # attach ourself for callback purposes # mapping the ODE universe self.position = PositionVector(self.body) self.velocity = VelocityVector(self.body) @@ -66,13 +69,19 @@ self.velocity.sync() # place self relative to the camera at proper zoom level camera = game.camera - self.rect.centerx = camera.rect.centerx + ((self.position.x - camera.position.x) * camera.zoom) - self.rect.centery = camera.rect.centery + ((self.position.y - camera.position.y) * camera.zoom) + self.rect.centerx = camera.rect.centerx + ( + (self.position.x - camera.position.x) * camera.zoom) + self.rect.centery = camera.rect.centery + ( + (self.position.y - camera.position.y) * camera.zoom) def kill(self): """Out damned sprite""" pygame.sprite.Sprite.kill(self) + # Disable the body and geom objects immediately so they + # are no longer considered by ode. self.body.disable() + if self.geom is not None: + self.geom.disable() def push(self, force): """Apply the force vector to the body for this frame""" Modified: eos.py =================================================================== --- eos.py 2007-01-17 08:57:40 UTC (rev 52) +++ eos.py 2007-01-19 10:30:21 UTC (rev 53) @@ -7,9 +7,12 @@ # $Date$ import sys +import pygame import game if __name__ == '__main__': game.init('--window' not in sys.argv[1:]) + start = pygame.time.get_ticks() game.play() + print 'Average fps:', game.frame_no * 1000.0/(game.time - start) Modified: game.py =================================================================== --- game.py 2007-01-17 08:57:40 UTC (rev 52) +++ game.py 2007-01-19 10:30:21 UTC (rev 53) @@ -24,6 +24,7 @@ # Other globally accessible things clock = None time = 0 # milliseconds since game began +frame_no = 0 # current frame number player = None camera = None ai = None @@ -63,7 +64,7 @@ camera = player = Player() camera.rect.center = screen.get_rect().center camera.zoom = 1 - ai = [AI(player) for i in xrange(random.randint(5,9))] + ai = [AI(player) for i in xrange(10)] def handle_events(): for event in pygame.event.get(): @@ -88,7 +89,7 @@ def play(): global universe, collision_space, screen, background, sprites - global fps, avg_fps, clock, time + global fps, avg_fps, clock, time, frame_no while handle_events(): collision_space.collide(None, check_collision) sprites.clear(screen, background) @@ -99,3 +100,4 @@ universe.quickStep(1.0 / avg_fps) time += clock.tick(fps) avg_fps = clock.get_fps() or fps + frame_no += 1 Modified: player.py =================================================================== --- player.py 2007-01-17 08:57:40 UTC (rev 52) +++ player.py 2007-01-19 10:30:21 UTC (rev 53) @@ -35,7 +35,7 @@ turn_accel = math.radians(0.18) # Cosmetic - shield_opacity = 100 # 0-255 + shield_opacity = 110 # 0-255 shield_flicker = 12 shield_fadeout = 200 shield_timeout = 1500 # millis shields stay active @@ -125,9 +125,9 @@ """ if keystate[K_UP]: if keystate[K_w]: - self.max_speed = Player.max_speed * self.speed_boost + self.max_speed = self.__class__.max_speed * self.speed_boost else: - self.max_speed = Player.max_speed + self.max_speed = self.__class__.max_speed self.thrust = Vector2D.unit(self.heading) * self.fw_thrust else: self.thrust *= 0 Modified: projectile.py =================================================================== --- projectile.py 2007-01-17 08:57:40 UTC (rev 52) +++ projectile.py 2007-01-19 10:30:21 UTC (rev 53) @@ -17,10 +17,10 @@ class FullereneCannon: - charge_rate = 2.0 # charge per second + charge_rate = 2.5 # charge per second max_charge = 4.5 min_charge = 0.25 - max_hold = 3 # Max time full charge holds in seconds + max_hold = 4 # Max time full charge holds in seconds def __init__(self, gunmount): self.charge_start = None Modified: vector.py =================================================================== --- vector.py 2007-01-17 08:57:40 UTC (rev 52) +++ vector.py 2007-01-19 10:30:21 UTC (rev 53) @@ -171,7 +171,17 @@ def __neg__(self): return Vector2D(-self.x, -self.y) + + def __eq__(self, other): + """Compare two vectors for equality + >>> Vector2D(4, 2) == Vector2D(4, 2) + True + >>> Vector2D(4, 2) == Vector2D(2, 4) + False + """ + return self.x == other.x and self.y == other.y + def clamp(self, k): if self.length > k: return self * (k / self.length) @@ -213,7 +223,21 @@ else: self._normal = self / self.length return self._normal + + def copy(self): + """Return a new vectory equal to self + >>> v = Vector2D(5, 6) + >>> v2 = v.copy() + >>> v.x == v2.x and v.y == v2.y and v.length == v2.length + True + """ + cp = Vector2D(self.x, self.y) + cp._radians = self._radians + cp._length = self._length + cp._normal = self._normal + return cp + @classmethod def unit(cls, radians): """Return the unit vector for a given heading in radians This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-25 09:10:21
|
Revision: 58 http://eos-game.svn.sourceforge.net/eos-game/?rev=58&view=rev Author: cduncan Date: 2007-01-25 01:10:19 -0800 (Thu, 25 Jan 2007) Log Message: ----------- Add vessel module which is a modular model for describing guided craft like ships and missiles. It abstracts out controls and systems that can be plugged in at runtime. Next I will create a ship configuration system so this can be plugged in to replace the existing player and ai ships Modified Paths: -------------- body.py player.py Added Paths: ----------- vessel.py Modified: body.py =================================================================== --- body.py 2007-01-25 08:52:14 UTC (rev 57) +++ body.py 2007-01-25 09:10:19 UTC (rev 58) @@ -27,8 +27,8 @@ the universe """ - mass = 0 # Mass in tons - radius = 0 # radius in meters + mass = 0.1 # Mass in tons + radius = 0.1 # radius in meters category = nothing # category for collision interaction collides_with = nothing # category bitmap this body collides with @@ -62,6 +62,10 @@ self.velocity = VelocityVector(self.body) # on screen size and positioning self.rect = pygame.Rect([position[0], position[1], 0, 0]) + + @property + def force(self): + return Vector2D(*self.body.getForce()[:2]) def update(self): """Calculate onscreen position and size""" Modified: player.py =================================================================== --- player.py 2007-01-25 08:52:14 UTC (rev 57) +++ player.py 2007-01-25 09:10:19 UTC (rev 58) @@ -15,7 +15,21 @@ from body import RoundBody, friend, nothing from vector import Vector2D, fullcircle, rightangle from media import RotatedImage, load_image +from vessel import Controls +class KeyboardControls(Controls): + + def update(self): + keystate = pygame.key.get_pressed() + self.thrust = keystate[K_UP] + self.turn = keystate[K_RIGHT] - keystate[K_LEFT] + self.fw_maneuver = keystate[K_w] + self.right_maneuver = keystate[K_a] + self.left_maneuver = keystate[K_d] + self.bw_maneuver = keystate[K_s] or keystate[K_DOWN] + self.shield = keystate[R_SHIFT] + self.weapons[0] = keystate[K_SPACE] + class Player(RoundBody): mass = 2 Added: vessel.py =================================================================== --- vessel.py (rev 0) +++ vessel.py 2007-01-25 09:10:19 UTC (rev 58) @@ -0,0 +1,546 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Vessel classes +# $Id$ + +import sys +import math +import random +import game +import pygame +from body import RoundBody +from vector import Vector2D, fullcircle, halfcircle, rightangle +from media import RotatedImage + +max_weapons = 5 + +class Controls: + """Static vessel control state""" + + thrust = False + fw_maneuver = False + left_maneuver = False + right_maneuver = False + bw_maneuver = False + + turn = 0 # -1 = left, 0 = stop, 1 = right + + shield = False + weapons = [False] * max_weapons + + def update(self): + """Update control state""" + pass + + +class Vessel(RoundBody): + """A controlled body + + Vessels consist of a hull containing controls and systems which can react + to the environment and relay information to other systems . The hull + determines which and how many systems may be present. Each frame the + systems read the controls and can affect the vessel in some way or perform + an action on behalf of the vessel. + + Systems perform functions like below: + + - Damage systems absorb damage. If the damage systems are depleted and + more damage is sustained, then other systems may be disabled or the ship + may be destroyed. + - Maneuvering systems allow the ship to turn and move. + - Weapon systems allow the ship to attack. + - Cargo systems allow the ship to transport cargo. + + Systems that have a graphical representation should subclass Sprite. + """ + + class_name = '' # Name of vessel class + image_name = 'ship.png' + hull_mass = 1 # Mass of hull in tons + hull_damage = 0 # Current damage to hull + disable_factor = 20 # disabled if damage exceeds this factor * hull_mass + system_damage = 0 # System damage accumulator + system_damage_threshold = 10 # How much damage to disable a system + max_speed = 0 + + control = Controls() + + def __init__(self, position, velocity=None, heading=None, systems=[]): + RoundBody.__init__(self, position, velocity) + self.vessel_img = RotatedImage(self.image_name) + self._sys = [] + self._damage_sys = [] + if heading is not None: + self.heading = float(heading) + else: + self.heading = random.random() * fullcircle + self.turning = 0 + for s in systems: + self.add_system(s) + + def update(self): + self.control.update() + for system in self._sys: + system.update_system() + if self.velocity.length > self.max_speed: + # If we are overspeed, bleed off a little + self.velocity *= 0.98 + self.heading += self.turning + self.image = self.vessel_img.rotated(self.heading) + self.rect = self.image.get_rect(center=self.rect.center) + + def add_system(self, system): + """Add a vessel system + + >>> game.init() + >>> class TestSys: mass = 5 + >>> v = Vessel((0,0)) + >>> v.mass = 1 + >>> len(list(v)) + 0 + >>> s = TestSys() + >>> v.add_system(s) + >>> len(list(v)) + 1 + >>> v.mass + 6 + """ + if self.allow_system(system): + self._sys.append(system) + self.calc_mass() + if hasattr(system, 'damage'): + self._damage_sys.append(system) + self._damage_sys.sort(key=lambda s: getattr(s, 'priority', 999)) + + def allow_system(self, system): + """Return true if the hull can accomodate the system""" + return system not in self + + def remove_system(self, system): + """Remove a vessel system + >>> game.init() + >>> class TestSys: mass = 10 + >>> sys = [TestSys(), TestSys()] + >>> v = Vessel((0,0), systems=sys) + >>> len(list(v)) + 2 + >>> v.mass + 21 + >>> v.remove_system(sys[1]) + >>> len(list(v)) + 1 + >>> v.mass + 11 + >>> v.remove_system(sys[0]) + >>> len(list(v)) + 0 + >>> v.mass + 1 + >>> v.remove_system(sys[0]) + Traceback (most recent call last): + ... + ValueError: list.remove(x): x not in list + """ + self._sys.remove(system) + self.calc_mass() + if system in self._damage_sys: + self._damage_sys.remove(system) + + def calc_mass(self): + """Calculate the vessel mass + + >>> game.init() + >>> class TestSys: mass = 7 + >>> v = Vessel((0,0), systems=[TestSys()]) + >>> v.hull_mass = 5 + >>> v.calc_mass() + >>> v.mass + 12 + """ + self.mass = sum(s.mass for s in self._sys) + self.hull_mass + massobj = self.body.getMass() + massobj.adjust(self.mass) + + def __iter__(self): + return iter(self._sys) + + def disable(self): + self.kill() + + def damage(self, value): + """Apply damage to the vessel + + >>> game.init() + >>> class DamageSys: + ... mass = 0 + ... level = 0 + ... def damage(self, d): + ... self.level += d / 2 + ... return d / 2 + ... + >>> d = DamageSys() + >>> v = Vessel((0,0), systems=[d]) + >>> v.damage(10) + >>> d.level + 5 + >>> v.hull_damage + 5 + """ + for s in self._damage_sys: + value = s.damage(value) + if not value: + break + if value: + # Apply residual damage to hull + self.hull_damage += value + if self.hull_damage > self.disable_factor * self.hull_mass: + self.disable() + else: + self.system_damage += value + while self.system_damage > self.system_damage_threshold: + random.choice(self._sys).disable() + self.system_damage -= self.system_damage_threshold + + def kill(self): + for system in self._sys: + if hasattr(system, 'kill'): + system.kill() + RoundBody.kill(self) + + +class System: + """Vessel system base class""" + + name = 'base system' + mass = 0 + priority = sys.maxint + enabled = True + + def update_system(self): + """Update system state, called once per frame""" + pass # overridden in subclasses + + def disable(self): + """disable system + + >>> s = System() + >>> s.enabled + True + >>> s.disable() + >>> s.enabled + False + """ + self.enabled = False + + def enable(self): + """enable system + + >>> s = System() + >>> s.disable() + >>> s.enabled + False + >>> s.enable() + >>> s.enabled + True + """ + self.enabled = True + + +class Shield(System, pygame.sprite.Sprite): + """Basic vessel shield system + + Shields have a current level, maximum level and regeneration rate. + When shields are damaged, the damage value is deducted from the shield + level. When the level reached zero, damage is no longer absorbed. + The shield level recovers at the regeneration rate (per sec) as long + as the shield system is not disabled. + """ + + name = 'energy shield' + mass = 0 + priority = 0 # Shields get hit first + + # Cosmetic + opacity = 110 # 0-255 + flicker = 12 # variance in opacity + fadeout = 200 # millis shields fade when turned off + timeout = 1500 # millis shields stay active + + def __init__(self, vessel, max_level, regeneration): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.vessel = vessel + self.max_level = self.level = float(max_level) + self.regeneration = float(regeneration) + self.rot_images = RotatedImage('shield.png') + self.time = 0 + self.enabled = True + + def disable(self): + self.level = 0 + self.enabled = False + + def show_shield(self): + """Show shields for timeout secs + + >>> game.init() + >>> s = Shield(None, 0, 0) + >>> s.time > game.time + False + >>> s.show_shield() + >>> s.time > game.time + True + """ + self.time = game.time + self.timeout + + def update_system(self): + """Update shield status and regenerate + + >>> game.init() + >>> game.avg_fps = 30 + >>> s = Shield(Vessel((0,0)), 10.0, 30.0) + >>> s.level == s.max_level + True + >>> s.update_system() + >>> s.level == s.max_level + True + + >>> s.level = 0 + >>> s.update_system() + >>> s.level + 1.0 + """ + if self.vessel.control.shield: + self.show_shield() + if self.level < self.max_level and self.regeneration and self.enabled: + self.level = min( + self.level + self.regeneration / game.avg_fps, self.max_level) + + def draw(self, surface): + if self.time >= game.time and self.level: + image = self.rot_images.rotated( + math.radians(game.time / 30)) + flicker = self.flicker + self.flicker * (self.max_level / self.level) + flicker = random.randint(0, flicker) + opacity = self.opacity * (self.level / self.max_level) + if self.time - game.time <= self.fadeout: + opacity *= (self.time - game.time) / self.fadeout + image.set_alpha(max(opacity - flicker, 10)) + shield_rect = image.get_rect(center=self.vessel.rect.center) + dirty = surface.blit(image, shield_rect) + return dirty + else: + return (0, 0, 0, 0) + + def damage(self, value): + """Damage shields, return damage not absorbed + + >>> game.init() + >>> s = Shield(None, 5, 0) + >>> s.level + 5.0 + >>> s.damage(3) + 0 + >>> s.level + 2.0 + >>> s.damage(3) + 1.0 + >>> s.level + 0 + """ + if value < self.level: + self.level -= value + self.show_shield() + return 0 + else: + value -= self.level + self.level = 0 + return value + + +class DirectionalThrusters(System): + """Simple directional thrust system""" + + name = 'directional thrusters' + + mass = 0.5 + mass_factor = 50 # Number of tons required to half turn acceleration + + def __init__(self, vessel, thrust, max_turn_rate): + """thrust is in radians per second per second for a one ton ship + max_turn_rate is in radians per second + """ + self.vessel = vessel + self.thrust = float(thrust) + self.max_turn_rate = float(max_turn_rate) + self.enabled = True + + def disable(self): + self.enabled = False + + def enable(self): + self.enabled = True + + @property + def accel(self): + """Per frame turn acceleration""" + return self.thrust / max(self.mass_factor / self.vessel.mass * 2, 1) / game.avg_fps + + def update_system(self): + """Update vessel turning based on control input + + >>> game.init() + >>> v = Vessel((0,0)) + >>> dt = DirectionalThrusters(v, 10, 2) + >>> v.add_system(dt) + >>> v.control.turn = 1 + >>> dt.update_system() + >>> v.turning == dt.accel + True + >>> v.turning = 0 + >>> v.control.turn = -1 + >>> dt.update_system() + >>> v.turning == -dt.accel + True + >>> for i in range(1000): dt.update_system() + >>> v.turning == -dt.max_turn_rate + True + """ + if self.vessel.control.turn and self.enabled: + self.vessel.turning += self.vessel.control.turn * self.accel + self.vessel.turning = min(max( + self.vessel.turning, -self.max_turn_rate), self.max_turn_rate) + elif self.vessel.turning > 0: + self.vessel.turning = max(self.vessel.turning - self.accel, 0) + elif self.vessel.turning < 0: + self.vessel.turning = min(self.vessel.turning + self.accel, 0) + + +class ManeuveringThrusters(System): + + name = 'maneuvering thrusters' + mass_factor = 0.1 # mass per unit thrust + + def __init__(self, vessel, thrust): + self.vessel = vessel + self.thrust = float(thrust) + self.mass = thrust * self.mass_factor + self.enabled = True + + def disable(self): + self.enabled = False + + def enable(self): + self.enabled = True + + def update_system(self): + """Translate control state into thrusting actions + + >>> game.init() + >>> v = Vessel((0,0), heading=0) + >>> mt = ManeuveringThrusters(v, 1) + >>> v.control.fw_maneuver = True + >>> mt.update_system() + >>> v.force.length > 0 + True + >>> v.force.radians == v.heading + True + + >>> v = Vessel((0,0), heading=0) + >>> mt = ManeuveringThrusters(v, 1) + >>> v.control.fw_maneuver = False + >>> v.control.right_maneuver = True + >>> mt.update_system() + >>> v.force.length > 0 + True + >>> v.force.radians == v.heading + rightangle + True + + >>> v = Vessel((0,0), heading=0) + >>> mt = ManeuveringThrusters(v, 1) + >>> v.control.right_maneuver = False + >>> v.control.left_maneuver = True + >>> mt.update_system() + >>> v.force.length > 0 + True + >>> v.force.radians == v.heading + halfcircle + rightangle + True + + >>> v = Vessel((0,0), heading=0) + >>> mt = ManeuveringThrusters(v, 1) + >>> v.control.left_maneuver = False + >>> v.control.bw_maneuver = True + >>> mt.update_system() + >>> v.force.length > 0 + True + >>> v.force.radians == v.heading + halfcircle + True + """ + if self.enabled: + if self.vessel.control.fw_maneuver: + self.vessel.push(Vector2D.unit(self.vessel.heading) * self.thrust) + if self.vessel.control.bw_maneuver: + self.vessel.push(Vector2D.unit(self.vessel.heading + halfcircle) * self.thrust) + if self.vessel.control.left_maneuver: + self.vessel.push(Vector2D.unit(self.vessel.heading - rightangle) * self.thrust) + if self.vessel.control.right_maneuver: + self.vessel.push(Vector2D.unit(self.vessel.heading + rightangle) * self.thrust) + + +class Engine(System): + """Basic thrust engine""" + + name = 'engine' + base_mass = 1 + mass_factor = 0.05 # mass per unit thrust + speed_boost = 1.6 + + def __init__(self, vessel, thrust): + self.vessel = vessel + self.thrust = float(thrust) + self.mass = self.base_mass + thrust * self.mass_factor + self.enabled = True + + def disable(self): + self.enabled = False + + def enable(self): + self.enabled = True + + def update_system(self): + """Translate control state into thrust actions + + >>> game.init() + >>> v = Vessel((0,0), heading=0) + >>> e = Engine(v, 10) + + >>> v.control.thrust = False + >>> e.update_system() + >>> v.force.length + 0.0 + + >>> v.control.thrust = True + >>> v.max_speed = 10 + >>> v.heading = 0 + >>> e.update_system() + >>> v.force.length > 0 + True + >>> v.force.radians == v.heading + True + """ + if self.vessel.control.thrust and self.enabled: + if self.vessel.control.fw_maneuver: + # Thrust + forward maneuver == boost + self.vessel.max_speed = self.vessel.__class__.max_speed * self.speed_boost + else: + self.vessel.max_speed = self.vessel.__class__.max_speed + self.vessel.push(Vector2D.unit(self.vessel.heading) * self.thrust) + + +if __name__ == '__main__': + """Run tests if executed directly""" + import sys, doctest + failed, count = doctest.testmod() + print 'Ran', count, 'test cases with', failed, 'failures' + sys.exit(failed) Property changes on: vessel.py ___________________________________________________________________ Name: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-07 03:04:02
|
Revision: 79 http://eos-game.svn.sourceforge.net/eos-game/?rev=79&view=rev Author: oberon7 Date: 2007-02-06 19:03:55 -0800 (Tue, 06 Feb 2007) Log Message: ----------- Work-in-progress on multiplayer support. Modified Paths: -------------- syncobj.py Added Paths: ----------- syncrandom.py Modified: syncobj.py =================================================================== --- syncobj.py 2007-02-07 02:37:41 UTC (rev 78) +++ syncobj.py 2007-02-07 03:03:55 UTC (rev 79) @@ -6,101 +6,59 @@ # $Id$ # $Date$ +import game import memcachepool -import threading import time -import Queue +from vessel import KeyboardControl + +_game_id = None _server = None _client_id = None -_ticker = threading.Event() - -def tick(): - _ticker.set() - _ticker.clear() - +_clients = None +_players = None def init(host='127.0.0.1', port='11211', debug=False): - global _server + """Establish multiplayer connections + """ + global _server, _game_id, _client_id, _clients, _players + # establish server connection _server = memcachepool.Client(['%s:%s' % (host, port)], debug=debug) + # determine game id + _game_id = 'game=%s' % 666 # FIXME + _server.add(_game_id, '0') + # determine our client id + _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) + # wait for other clients to connect + last_client_count = None + while True: + client_count = int(_server.get(_game_id)) + if client_count == last_client_count: + break + last_client_count = client_count + time.sleep(10) + # sync up with other clients + _clients = ['%s,client=%s' % (_game_id, i+1) + for i in xrange(client_count)] + _players = [c == _client_id and game.player + or KeyboardControl.new_player() + for c in _clients] - for target in [ - Memcached.TODO, - Memcached.fill_ids, - Memcached.publish, - Memcached.subscribe, - ]: - t = threading.Thread(target=target) - t.setDaemon(True) - t.start() -class Memcached: - """Base class for objects that are synchronized using - memcached. Instances of this class are either passive - or authoritative. Passive objects strive to track the - state stored within the memcached server. Authoritative - objects post their state to the memcached server. +def send_keystate(frame_no, keystate): + """Send this client's keystate to the server """ - _ids = Queue.Queue(100) - _publish = {} - _subscribe = {} + key = '%s,frame=%d' % (_client_id, frame_no) + _server.add(key, keystate) - def __init__(self, id=None): - if id is None: - self.id = Memcached._ids.get(block=False) - Memcached._publish[self.id] = self - else: - self.id = id - def destroy(self): - if Memcached._publish.has_key(self.id): - del Memcached._publish[self.id] - - def get_state(self): - raise NotImplementedError - - def set_state(self, state): - raise NotImplementedError - - @classmethod - def TODO(cls): - global _client_id - # we use the "add" memcache call because it is a no-op - # in the cases where another client called it first - _server.add('client_id', '0') - _client_id = _server.incr('client_id') - while True: - _ticker.wait() - max_client_id = int(_server.get('client_id')) - client_ids = [str(i) for i in xrange(1, max_client_id+1) if i != _client_id] - for id_list in _server.get_multi(client_ids).values(): - for id in id_list: - cls._subscribe.setdefault(id, None) - time.sleep(1) - - @classmethod - def fill_ids(cls): - # we use the "add" memcache call because it is a no-op - # in the cases where another client called it first - _server.add('obj_id', '0') - while True: - id = _server.incr('obj_id') - cls._ids.put(id, block=True) - - @classmethod - def publish(cls): - while True: - _ticker.wait() - _server.set(str(_client_id), cls._publish.keys()) - for id, obj in cls._publish.items(): - _server.set(id, obj.get_state()) - - @classmethod - def subscribe(cls): - while True: - _ticker.wait() - id_list = cls._subscribe.keys() - # we use the get_multi method to do one network query - # to retrieve the state for all passive objects - for id, state in _server.get_multi(id_list): - cls._subscribe[id].set_state(state) - +def step(frame_no): + """Retrieve all keystates from the server + """ + keys = ['%s,frame=%d' % (oc, frame_no) for oc in _clients] + while keys: + results = _server.get_multi(keys) + for key, value in results.items(): + client_id = key.rsplit(',', 1)[0] + index = _clients.index(client_id) + _players[index].control.set_keystate(value) + keys.remove(key) Added: syncrandom.py =================================================================== --- syncrandom.py (rev 0) +++ syncrandom.py 2007-02-07 03:03:55 UTC (rev 79) @@ -0,0 +1,32 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (client) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Frame synchronized randomness +# $Id$ +# $Date$ + +import game +from random import Random + +class SyncRandom(Random): + + def random(self): + # Prior to every generation of randomness we + # fix the seed to be the number of the current + # frame. This is not particularly random, and + # 100% predictable within any given frame, but + # for the time being it's good enough. + Random.seed(self, game.frame_no) + return Random.random(self) + +_random = SyncRandom() + +def choice(*args, **kwargs): + return _random.choice(*args, **kwargs) + +def randint(*args, **kwargs): + return _random.randint(*args, **kwargs) + +def random(*args, **kwargs): + return _random.random(*args, **kwargs) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-09 05:40:06
|
Revision: 85 http://eos-game.svn.sourceforge.net/eos-game/?rev=85&view=rev Author: oberon7 Date: 2007-02-08 21:40:02 -0800 (Thu, 08 Feb 2007) Log Message: ----------- Refactoring of network synchronization code. Modified Paths: -------------- ai.py beam.py eos.py game.py media.py namegen.py projectile.py scimitar.py sprite.py stars.py staticbody.py syncrandom.py vector.py vessel.py Added Paths: ----------- syncclient.py syncserver.py syncstate.py Removed Paths: ------------- memcachepool.py syncobj.py Property Changed: ---------------- ai.py body.py eos.py game.py media.py namegen.py projectile.py scimitar.py setup.py sprite.py stars.py staticbody.py syncrandom.py vector.py Modified: ai.py =================================================================== --- ai.py 2007-02-09 05:26:51 UTC (rev 84) +++ ai.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,19 +4,19 @@ # A.I. # $Id$ -# $Date$ -import random import math from pygame.locals import * import game import body +import syncrandom from vector import Vector2D, diagonal, halfcircle, rightangle, fullcircle from vessel import Control, Vessel, DirectionalThrusters class AIControl(Control): def __init__(self, vessel, target): + Control.__init__(self) self.vessel = vessel # override vessel collision handler with our own #self.vessel.collide = self.collide @@ -24,7 +24,7 @@ # cache some vessel stats self.target = target self.close_vessels = [] - self.steerfunc = random.choice([self.seek, self.pursue]) + self.steerfunc = syncrandom.choice([self.seek, self.pursue]) def seek_position(self, target_position): """Return desired velocity towards a fixed position Property changes on: ai.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: beam.py =================================================================== --- beam.py 2007-02-09 05:26:51 UTC (rev 84) +++ beam.py 2007-02-09 05:40:02 UTC (rev 85) @@ -5,11 +5,11 @@ # Beam weapons # $Id$ -import random import ode import game import pygame import body +import syncrandom import vessel from vector import Vector2D @@ -46,7 +46,7 @@ dx *= self.length * camera.zoom dy *= self.length * camera.zoom self.targeted = False - c = random.randint(1, 4) + c = syncrandom.randint(1, 4) return pygame.draw.line(surface, (self.color[0]/c, self.color[1]/c, self.color[2]/c), (sx, sy), (sx + dx, sy + dy), self.width) else: Property changes on: body.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: eos.py =================================================================== --- eos.py 2007-02-09 05:26:51 UTC (rev 84) +++ eos.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Main program script # $Id$ -# $Date$ import sys import pygame Property changes on: eos.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: game.py =================================================================== --- game.py 2007-02-09 05:26:51 UTC (rev 84) +++ game.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,13 +4,15 @@ # Global game elements # $Id$ -# $Date$ import math -import random import ode import pygame +import syncrandom +import syncserver +import syncstate from pygame.locals import * +from time import sleep fps = 32 # Desired fps avg_fps = fps # Running average of actual fps @@ -54,7 +56,7 @@ print "Could not find ideal display mode, using", mode screen = pygame.display.set_mode(mode, FULLSCREEN, 16) else: - screen = pygame.display.set_mode((1024, 768)) + screen = pygame.display.set_mode((640, 480)) screen_rect = screen.get_rect() pygame.display.set_caption('Eos') background = pygame.Surface(screen.get_size()) @@ -72,18 +74,24 @@ sprites = RenderedGroup() StarField(screen.get_rect()) Planet('earth-east.png', 0, 0) - camera = player = Vessel.load(random.choice(['vessels/naree/lotus'])) - player.setup_collision(body.friend, body.nothing) - player.control = KeyboardControl() + camera = player = KeyboardControl.new_player() camera.rect.center = screen.get_rect().center camera.zoom = 1 + + # Establish networking + server = syncserver.Server() + while server.isAlive() and not server.isReady(): + sleep(1) + syncstate.init(server.host, server.port, 0) + + # Create AI for i in range(5): - ship = random.choice(['vessels/naree/cress', 'vessels/naree/lotus']) + ship = syncrandom.choice(['vessels/naree/cress', 'vessels/naree/lotus']) ai = AIVessel.load(ship, target=player) - ai.position.x, ai.position.y = random.randint(-1000, 1000), random.randint(-1000,1000) + ai.position.x, ai.position.y = syncrandom.randint(-1000*i, 1000*i), syncrandom.randint(-1000*i,1000*i) for i in range(8): ai = AIVessel.load('vessels/naree/gnat', target=player) - ai.position.x, ai.position.y = random.randint(-1000, 1000), random.randint(-1000,1000) + ai.position.x, ai.position.y = syncrandom.randint(-1000*i, 1000*i), syncrandom.randint(-1000*i,1000*i) def handle_events(): for event in pygame.event.get(): @@ -124,9 +132,10 @@ raise def play(): - global universe, collision_space, screen, background, sprites - global fps, avg_fps, clock, time, frame_no + global universe, collision_space, screen, background + global sprites, fps, avg_fps, clock, time, frame_no while handle_events(): + syncstate.send_keystate(frame_no, pygame.key.get_pressed()) collision_space.collide(None, check_collision) sprites.clear(screen, background) sprites.update() @@ -136,4 +145,5 @@ universe.quickStep(1.0 / avg_fps) time += clock.tick(fps) avg_fps = clock.get_fps() or fps + syncstate.step(frame_no) frame_no += 1 Property changes on: game.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: media.py =================================================================== --- media.py 2007-02-09 05:26:51 UTC (rev 84) +++ media.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Graphics and sound utilities # $Id$ -# $Date$ import math import pygame Property changes on: media.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Deleted: memcachepool.py =================================================================== --- memcachepool.py 2007-02-09 05:26:51 UTC (rev 84) +++ memcachepool.py 2007-02-09 05:40:02 UTC (rev 85) @@ -1,90 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (client) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Pooling memcached client & server wrapper -# $Id$ - -import atexit -import memcache -import os -import signal -import subprocess - -class Client: - """Memcached client that magically grows a pool of itself. This - is necessary because the standard client is not threadsafe. - - Note that returning the memcached clients to the pool doesn't - happen when the underlying method raises an exception. This is - intentional, it's how we shelter ourself from curdled connections. - """ - - def __init__(self, *args, **kwargs): - self._pool = [] - self._init_args = args - self._init_kwargs = kwargs - - def _acquire(self): - try: - return self._pool.pop() - except IndexError: - return memcache.Client(*self._init_args, **self._init_kwargs) - - def _release(self, client): - self._pool.append(client) - - def add(self, *args, **kwargs): - client = self._acquire() - result = client.add(*args, **kwargs) - self._release(client) - return result - - def get(self, *args, **kwargs): - client = self._acquire() - result = client.get(*args, **kwargs) - self._release(client) - return result - - def get_multi(self, *args, **kwargs): - client = self._acquire() - result = client.get_multi(*args, **kwargs) - self._release(client) - return result - - def incr(self, *args, **kwargs): - client = self._acquire() - result = client.incr(*args, **kwargs) - self._release(client) - return result - - def set(self, *args, **kwargs): - client = self._acquire() - result = client.set(*args, **kwargs) - self._release(client) - return result - -class Server: - """Wrapper around a memcached server process - """ - - def __init__(self, host='127.0.0.1', port='11211'): - self.host = host - self.port = port - self.proc = subprocess.Popen(['memcached', '-l', self.host, '-p', self.port]) - atexit.register(self.kill) - signal.signal(signal.SIGTERM, self.kill) - signal.signal(signal.SIGQUIT, self.kill) - signal.signal(signal.SIGABRT, self.kill) - - def client(self, debug=False): - return Client([self.host+':'+self.port], debug=debug) - - def isAlive(self): - return self.proc.returncode is None - - def isReady(self): - return 0 != self.client().add('hello', 'world') - - def kill(self, *args): - os.kill(self.proc.pid, signal.SIGTERM) Modified: namegen.py =================================================================== --- namegen.py 2007-02-09 05:26:51 UTC (rev 84) +++ namegen.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,9 +4,8 @@ # Random name generation # $Id$ -# $Date$ -import random +import syncrandom def makeNameTable(nameFile): @@ -29,9 +28,9 @@ def makeName(table, maxletters=16): """Return a randomized string using a Markov chain table. """ - name = random.choice([k for k in table.keys() if k.startswith(' ')]) + name = syncrandom.choice([k for k in table.keys() if k.startswith(' ')]) while len(name) < maxletters: - triplet = random.choice(table[name[-2:]]) + triplet = syncrandom.choice(table[name[-2:]]) if triplet is None: break name += triplet return name.strip() Property changes on: namegen.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: projectile.py =================================================================== --- projectile.py 2007-02-09 05:26:51 UTC (rev 84) +++ projectile.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,13 +4,12 @@ # Projectile weapons # $Id$ -# $Date$ -from random import randint import math import pygame from pygame.locals import * import game +import syncrandom from body import RoundBody, everything from vector import Vector2D, fullcircle, halfcircle, diagonal import vessel @@ -115,7 +114,7 @@ partsize = self.partsize * game.camera.zoom radius = self.charge / 2 * game.camera.zoom rects = [self.rect] - bg = randint(0, self.colormax) + bg = syncrandom.randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg), (self.rect.center, (partsize, partsize))) for i in self.step: @@ -124,7 +123,7 @@ self.rect.centery + direction.y * radius, partsize, partsize) rects.append(part) - bg = randint(0, self.colormax) + bg = syncrandom.randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg) , part) self.rot += self.partangle self.rot += self.rot_per_frame Property changes on: projectile.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: scimitar.py =================================================================== --- scimitar.py 2007-02-09 05:26:51 UTC (rev 84) +++ scimitar.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Scimitar Ship # $Id$ -# $Date$ import pygame from pygame.locals import * Property changes on: scimitar.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Property changes on: setup.py ___________________________________________________________________ Name: svn:keywords + Id Modified: sprite.py =================================================================== --- sprite.py 2007-02-09 05:26:51 UTC (rev 84) +++ sprite.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Sprite extensions # $Id$ -# $Date$ import pygame Property changes on: sprite.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: stars.py =================================================================== --- stars.py 2007-02-09 05:26:51 UTC (rev 84) +++ stars.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Starfield background # $Id$ -# $Date$ from random import randint import pygame Property changes on: stars.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: staticbody.py =================================================================== --- staticbody.py 2007-02-09 05:26:51 UTC (rev 84) +++ staticbody.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Planets and other static bodies # $Id$ -# $Date$ import pygame from pygame.constants import * Property changes on: staticbody.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Copied: syncclient.py (from rev 84, memcachepool.py) =================================================================== --- syncclient.py (rev 0) +++ syncclient.py 2007-02-09 05:40:02 UTC (rev 85) @@ -0,0 +1,61 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (client) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Network synchronized client +# $Id$ + +import memcache + +class Client: + """Memcached client that magically grows a pool of itself. This + is necessary because the standard client is not threadsafe. + + Note that returning the memcached clients to the pool doesn't + happen when the underlying method raises an exception. This is + intentional, it's how we shelter ourself from curdled connections. + """ + + def __init__(self, *args, **kwargs): + self._pool = [] + self._init_args = args + self._init_kwargs = kwargs + + def _acquire(self): + try: + return self._pool.pop() + except IndexError: + return memcache.Client(*self._init_args, **self._init_kwargs) + + def _release(self, client): + self._pool.append(client) + + def add(self, *args, **kwargs): + client = self._acquire() + result = client.add(*args, **kwargs) + self._release(client) + return result + + def get(self, *args, **kwargs): + client = self._acquire() + result = client.get(*args, **kwargs) + self._release(client) + return result + + def get_multi(self, *args, **kwargs): + client = self._acquire() + result = client.get_multi(*args, **kwargs) + self._release(client) + return result + + def incr(self, *args, **kwargs): + client = self._acquire() + result = client.incr(*args, **kwargs) + self._release(client) + return result + + def set(self, *args, **kwargs): + client = self._acquire() + result = client.set(*args, **kwargs) + self._release(client) + return result Deleted: syncobj.py =================================================================== --- syncobj.py 2007-02-09 05:26:51 UTC (rev 84) +++ syncobj.py 2007-02-09 05:40:02 UTC (rev 85) @@ -1,61 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (client) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Memcached synchronized objects -# $Id$ -# $Date$ - -import game -import memcachepool -import time -from vessel import KeyboardControl - - -_game_id = None -_server = None -_client_id = None -_clients = None -_players = None -def init(host='127.0.0.1', port='11211', debug=False): - """Establish multiplayer connections - """ - global _server, _game_id, _client_id, _clients, _players - # establish server connection - _server = memcachepool.Client(['%s:%s' % (host, port)], debug=debug) - # determine game id - _game_id = 'game=%s' % 666 # FIXME - _server.add(_game_id, '0') - # determine our client id - _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) - # wait for other clients to connect - print 'Waiting for other clients...', - time.sleep(7) - client_count = int(_server.get(_game_id)) - print ' %d connected.' % client_count - # sync up with other clients - _clients = ['%s,client=%s' % (_game_id, i+1) - for i in xrange(client_count)] - _players = [c == _client_id and game.player - or KeyboardControl.new_player() - for c in _clients] - - -def send_keystate(frame_no, keystate): - """Send this client's keystate to the server - """ - key = '%s,frame=%d' % (_client_id, frame_no) - _server.add(key, keystate) - - -def step(frame_no): - """Retrieve all keystates from the server - """ - keys = ['%s,frame=%d' % (oc, frame_no) for oc in _clients] - while keys: - results = _server.get_multi(keys) - for key, value in results.items(): - client_id = key.rsplit(',', 1)[0] - index = _clients.index(client_id) - _players[index].control.set_keystate(value) - keys.remove(key) Modified: syncrandom.py =================================================================== --- syncrandom.py 2007-02-09 05:26:51 UTC (rev 84) +++ syncrandom.py 2007-02-09 05:40:02 UTC (rev 85) @@ -2,9 +2,8 @@ ## Copyright (client) 2007 Casey Duncan and contributors ## See LICENSE.txt for licensing details -# Frame synchronized randomness +# Network synchronized randomness # $Id$ -# $Date$ import game from random import Random Property changes on: syncrandom.py ___________________________________________________________________ Name: svn:keywords + Id Copied: syncserver.py (from rev 84, memcachepool.py) =================================================================== --- syncserver.py (rev 0) +++ syncserver.py 2007-02-09 05:40:02 UTC (rev 85) @@ -0,0 +1,37 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (client) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Network synchronized server +# $Id$ + +import atexit +import os +import signal +import subprocess +import syncclient + +class Server: + """Wrapper around a memcached server process + """ + + def __init__(self, host='127.0.0.1', port='11211'): + self.host = host + self.port = port + self.proc = subprocess.Popen(['memcached', '-l', self.host, '-p', self.port]) + atexit.register(self.kill) + signal.signal(signal.SIGTERM, self.kill) + signal.signal(signal.SIGQUIT, self.kill) + signal.signal(signal.SIGABRT, self.kill) + + def client(self, debug=False): + return syncclient.Client([self.host+':'+self.port], debug=debug) + + def isAlive(self): + return self.proc.returncode is None + + def isReady(self): + return 0 != self.client().add('hello', 'world') + + def kill(self, *args): + os.kill(self.proc.pid, signal.SIGTERM) Copied: syncstate.py (from rev 82, syncobj.py) =================================================================== --- syncstate.py (rev 0) +++ syncstate.py 2007-02-09 05:40:02 UTC (rev 85) @@ -0,0 +1,60 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (client) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Network synchronized state +# $Id$ + +import game +import syncclient +import time +from vessel import KeyboardControl + + +_game_id = None +_server = None +_client_id = None +_clients = None +_players = None +def init(host, port, wait, debug=True): + """Establish multiplayer connections + """ + global _server, _game_id, _client_id, _clients, _players + # establish server connection + _server = syncclient.Client(['%s:%s' % (host, port)], debug=debug) + # determine game id + _game_id = 'game=%s' % 666 # FIXME + _server.add(_game_id, '0') + # determine our client id + _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) + # wait for other clients to connect + print 'Waiting for other clients...', + time.sleep(wait) + client_count = int(_server.get(_game_id)) + print ' %d connected.' % client_count + # sync up with other clients + _clients = ['%s,client=%s' % (_game_id, i+1) + for i in xrange(client_count)] + _players = [c == _client_id and game.player + or KeyboardControl.new_player() + for c in _clients] + + +def send_keystate(frame_no, keystate): + """Send this client's keystate to the server + """ + key = '%s,frame=%d' % (_client_id, frame_no) + _server.add(key, keystate) + + +def step(frame_no): + """Retrieve all keystates from the server + """ + keys = ['%s,frame=%d' % (oc, frame_no) for oc in _clients] + while keys: + results = _server.get_multi(keys) + for key, value in results.items(): + client_id = key.rsplit(',', 1)[0] + index = _clients.index(client_id) + _players[index].control.set_keystate(value) + keys.remove(key) Property changes on: syncstate.py ___________________________________________________________________ Name: svn:keywords + Id Modified: vector.py =================================================================== --- vector.py 2007-02-09 05:26:51 UTC (rev 84) +++ vector.py 2007-02-09 05:40:02 UTC (rev 85) @@ -4,7 +4,6 @@ # Two dimensional vectors # $Id$ -# $Date$ import math Property changes on: vector.py ___________________________________________________________________ Name: svn:keywords - Id Date + Id Modified: vessel.py =================================================================== --- vessel.py 2007-02-09 05:26:51 UTC (rev 84) +++ vessel.py 2007-02-09 05:40:02 UTC (rev 85) @@ -5,16 +5,16 @@ # Vessel classes # $Id$ -import sys -import math -import random +import body import ConfigParser import game +import math import pygame +import syncrandom +import sys from pygame.locals import * -from body import RoundBody +from media import RotatedImage from vector import Vector2D, fullcircle, halfcircle, rightangle -from media import RotatedImage max_weapons = 5 _empty_rect = pygame.rect.Rect(0, 0, 0, 0) @@ -33,6 +33,9 @@ shield = False weapons = [False] * max_weapons + def __init__(self): + weapons = Control.weapons[:] # shallow copy + def update(self): """Update control state""" pass @@ -40,8 +43,7 @@ class KeyboardControl(Control): - def update(self): - keystate = pygame.key.get_pressed() + def set_keystate(self, keystate): self.thrust = keystate[K_UP] self.turn = keystate[K_RIGHT] - keystate[K_LEFT] self.fw_maneuver = keystate[K_w] @@ -51,12 +53,19 @@ self.shield = keystate[K_RSHIFT] self.weapons[0] = keystate[K_SPACE] + @classmethod + def new_player(cls): + player = Vessel.load('vessels/naree/lotus') + player.setup_collision(body.friend, body.nothing) + player.control = KeyboardControl() + return player + class VesselConfigError(Exception): """Vessel configuration file error""" -class Vessel(RoundBody): +class Vessel(body.RoundBody): """A controlled body Vessels consist of a hull containing controls and systems which can react @@ -96,7 +105,7 @@ self.offscreen_img = RotatedImage('pointy-red.gif') img_rect = self.vessel_img.get_rect() self.radius = max(img_rect.width, img_rect.height) / 2 - RoundBody.__init__(self) + body.RoundBody.__init__(self) self.vessel_name = vessel_name self.vessel_class = vessel_class self.vessel_type = vessel_type @@ -108,7 +117,7 @@ self._sys = [] self._damage_sys = [] self.weapons = [] - self.heading = random.random() * fullcircle + self.heading = syncrandom.random() * fullcircle @classmethod def load(cls, config_file, **kw): @@ -192,7 +201,7 @@ return vessel def update(self): - RoundBody.update(self) + body.RoundBody.update(self) self.control.update() for weapon, ctrl_state in zip(self.weapons, self.control.weapons): weapon.firing = ctrl_state @@ -336,7 +345,7 @@ else: self.system_damage += value while self.system_damage > self.system_damage_threshold: - system = random.choice(self._sys) + system = syncrandom.choice(self._sys) system.disable() self.system_damage -= self.system_damage_threshold @@ -344,7 +353,7 @@ for system in self._sys: if hasattr(system, 'kill'): system.kill() - RoundBody.kill(self) + body.RoundBody.kill(self) class System: @@ -471,7 +480,7 @@ image = self.rot_images.rotated( math.radians(game.time / 30)) flicker = int(self.flicker + self.flicker * (self.max_level / self.level)) - flicker = random.randint(0, flicker) + flicker = syncrandom.randint(0, flicker) opacity = self.opacity * (self.level / self.max_level / 2) + self.opacity if self.time - game.time <= self.fadeout: opacity *= (self.time - game.time) / self.fadeout This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-09 05:56:35
|
Revision: 86 http://eos-game.svn.sourceforge.net/eos-game/?rev=86&view=rev Author: oberon7 Date: 2007-02-08 21:56:33 -0800 (Thu, 08 Feb 2007) Log Message: ----------- Minor network synchronization cleanup. Modified Paths: -------------- syncserver.py syncstate.py Modified: syncserver.py =================================================================== --- syncserver.py 2007-02-09 05:40:02 UTC (rev 85) +++ syncserver.py 2007-02-09 05:56:33 UTC (rev 86) @@ -19,6 +19,8 @@ self.host = host self.port = port self.proc = subprocess.Popen(['memcached', '-l', self.host, '-p', self.port]) + if not self.isAlive(): + raise RuntimeError, 'memcached server failed to start' atexit.register(self.kill) signal.signal(signal.SIGTERM, self.kill) signal.signal(signal.SIGQUIT, self.kill) Modified: syncstate.py =================================================================== --- syncstate.py 2007-02-09 05:40:02 UTC (rev 85) +++ syncstate.py 2007-02-09 05:56:33 UTC (rev 86) @@ -28,10 +28,10 @@ # determine our client id _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) # wait for other clients to connect - print 'Waiting for other clients...', + print 'Waiting for clients...', time.sleep(wait) client_count = int(_server.get(_game_id)) - print ' %d connected.' % client_count + print '%d connected.' % client_count # sync up with other clients _clients = ['%s,client=%s' % (_game_id, i+1) for i in xrange(client_count)] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-09 06:32:48
|
Revision: 87 http://eos-game.svn.sourceforge.net/eos-game/?rev=87&view=rev Author: oberon7 Date: 2007-02-08 22:32:45 -0800 (Thu, 08 Feb 2007) Log Message: ----------- Collapse network synchronization into one module. Modified Paths: -------------- ai.py beam.py game.py namegen.py projectile.py vessel.py Added Paths: ----------- netsync.py Removed Paths: ------------- syncclient.py syncrandom.py syncserver.py syncstate.py Modified: ai.py =================================================================== --- ai.py 2007-02-09 05:56:33 UTC (rev 86) +++ ai.py 2007-02-09 06:32:45 UTC (rev 87) @@ -9,7 +9,7 @@ from pygame.locals import * import game import body -import syncrandom +import netsync from vector import Vector2D, diagonal, halfcircle, rightangle, fullcircle from vessel import Control, Vessel, DirectionalThrusters @@ -24,7 +24,7 @@ # cache some vessel stats self.target = target self.close_vessels = [] - self.steerfunc = syncrandom.choice([self.seek, self.pursue]) + self.steerfunc = netsync.choice([self.seek, self.pursue]) def seek_position(self, target_position): """Return desired velocity towards a fixed position Modified: beam.py =================================================================== --- beam.py 2007-02-09 05:56:33 UTC (rev 86) +++ beam.py 2007-02-09 06:32:45 UTC (rev 87) @@ -9,7 +9,7 @@ import game import pygame import body -import syncrandom +import netsync import vessel from vector import Vector2D @@ -46,7 +46,7 @@ dx *= self.length * camera.zoom dy *= self.length * camera.zoom self.targeted = False - c = syncrandom.randint(1, 4) + c = netsync.randint(1, 4) return pygame.draw.line(surface, (self.color[0]/c, self.color[1]/c, self.color[2]/c), (sx, sy), (sx + dx, sy + dy), self.width) else: Modified: game.py =================================================================== --- game.py 2007-02-09 05:56:33 UTC (rev 86) +++ game.py 2007-02-09 06:32:45 UTC (rev 87) @@ -6,11 +6,9 @@ # $Id$ import math +import netsync import ode import pygame -import syncrandom -import syncserver -import syncstate from pygame.locals import * from time import sleep @@ -78,21 +76,21 @@ camera.rect.center = screen.get_rect().center camera.zoom = 1 - # Establish networking - server = syncserver.Server() - while server.isAlive() and not server.isReady(): - sleep(1) - syncstate.init(server.host, server.port, 0) - # Create AI for i in range(5): - ship = syncrandom.choice(['vessels/naree/cress', 'vessels/naree/lotus']) + ship = netsync.choice(['vessels/naree/cress', 'vessels/naree/lotus']) ai = AIVessel.load(ship, target=player) - ai.position.x, ai.position.y = syncrandom.randint(-1000*i, 1000*i), syncrandom.randint(-1000*i,1000*i) + ai.position.x, ai.position.y = netsync.randint(-1000*i, 1000*i), netsync.randint(-1000*i,1000*i) for i in range(8): ai = AIVessel.load('vessels/naree/gnat', target=player) - ai.position.x, ai.position.y = syncrandom.randint(-1000*i, 1000*i), syncrandom.randint(-1000*i,1000*i) + ai.position.x, ai.position.y = netsync.randint(-1000*i, 1000*i), netsync.randint(-1000*i,1000*i) + # Establish networking + server = netsync.Server() + while server.isAlive() and not server.isReady(): + sleep(1) + netsync.init(server.host, server.port, 0) + def handle_events(): for event in pygame.event.get(): if event.type == QUIT or ( @@ -135,7 +133,7 @@ global universe, collision_space, screen, background global sprites, fps, avg_fps, clock, time, frame_no while handle_events(): - syncstate.send_keystate(frame_no, pygame.key.get_pressed()) + netsync.send_keystate(frame_no, pygame.key.get_pressed()) collision_space.collide(None, check_collision) sprites.clear(screen, background) sprites.update() @@ -145,5 +143,5 @@ universe.quickStep(1.0 / avg_fps) time += clock.tick(fps) avg_fps = clock.get_fps() or fps - syncstate.step(frame_no) + netsync.step(frame_no) frame_no += 1 Modified: namegen.py =================================================================== --- namegen.py 2007-02-09 05:56:33 UTC (rev 86) +++ namegen.py 2007-02-09 06:32:45 UTC (rev 87) @@ -5,7 +5,7 @@ # Random name generation # $Id$ -import syncrandom +import netsync def makeNameTable(nameFile): @@ -28,9 +28,9 @@ def makeName(table, maxletters=16): """Return a randomized string using a Markov chain table. """ - name = syncrandom.choice([k for k in table.keys() if k.startswith(' ')]) + name = netsync.choice([k for k in table.keys() if k.startswith(' ')]) while len(name) < maxletters: - triplet = syncrandom.choice(table[name[-2:]]) + triplet = netsync.choice(table[name[-2:]]) if triplet is None: break name += triplet return name.strip() Copied: netsync.py (from rev 86, syncstate.py) =================================================================== --- netsync.py (rev 0) +++ netsync.py 2007-02-09 06:32:45 UTC (rev 87) @@ -0,0 +1,163 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Network synchronization +# $Id$ + +import atexit +import game +import memcache +import os +import signal +import subprocess +import time +import vessel +from random import Random + +_server = None +_game_id = None +_client_id = None +_clients = None +_players = None + +def init(host, port, wait, debug=True): + global _server, _game_id, _client_id, _clients, _players + # establish server connection + _server = Client(['%s:%s' % (host, port)], debug=debug) + # determine game id + _game_id = 'game=%s' % 666 # FIXME + _server.add(_game_id, '0') + # determine our client id + _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) + # wait for other clients to connect + print 'Waiting for clients...', + time.sleep(wait) + client_count = int(_server.get(_game_id)) + print '%d connected.' % client_count + # sync up with other clients + _clients = ['%s,client=%s' % (_game_id, i+1) + for i in xrange(client_count)] + _players = [c == _client_id and game.player + or vessel.KeyboardControl.new_player() + for c in _clients] + +def send_keystate(frame_no, keystate): + """Send this client's keystate to the server + """ + key = '%s,frame=%d' % (_client_id, frame_no) + _server.add(key, keystate) + +def step(frame_no): + """Retrieve all keystates from the server + """ + keys = ['%s,frame=%d' % (oc, frame_no) for oc in _clients] + while keys: + results = _server.get_multi(keys) + for key, value in results.items(): + client_id = key.rsplit(',', 1)[0] + index = _clients.index(client_id) + _players[index].control.set_keystate(value) + keys.remove(key) + +class Client: + """Memcached client that magically grows a pool of itself. This + is necessary because the standard client is not threadsafe. + + Note that returning the memcached clients to the pool doesn't + happen when the underlying method raises an exception. This is + intentional, it's how we shelter ourself from curdled connections. + """ + + def __init__(self, *args, **kwargs): + self._pool = [] + self._init_args = args + self._init_kwargs = kwargs + + def _acquire(self): + try: + return self._pool.pop() + except IndexError: + return memcache.Client(*self._init_args, **self._init_kwargs) + + def _release(self, client): + self._pool.append(client) + + def add(self, *args, **kwargs): + client = self._acquire() + result = client.add(*args, **kwargs) + self._release(client) + return result + + def get(self, *args, **kwargs): + client = self._acquire() + result = client.get(*args, **kwargs) + self._release(client) + return result + + def get_multi(self, *args, **kwargs): + client = self._acquire() + result = client.get_multi(*args, **kwargs) + self._release(client) + return result + + def incr(self, *args, **kwargs): + client = self._acquire() + result = client.incr(*args, **kwargs) + self._release(client) + return result + + def set(self, *args, **kwargs): + client = self._acquire() + result = client.set(*args, **kwargs) + self._release(client) + return result + +class Server: + """Wrapper around a memcached server process + """ + + def __init__(self, host='127.0.0.1', port='11211'): + self.host = host + self.port = port + self.proc = subprocess.Popen(['memcached', '-l', self.host, '-p', self.port]) + if not self.isAlive(): + raise RuntimeError, 'memcached server failed to start' + atexit.register(self.kill) + signal.signal(signal.SIGTERM, self.kill) + signal.signal(signal.SIGQUIT, self.kill) + signal.signal(signal.SIGABRT, self.kill) + + def client(self, debug=False): + return Client([self.host+':'+self.port], debug=debug) + + def isAlive(self): + return self.proc.returncode is None + + def isReady(self): + return 0 != self.client().add('hello', 'world') + + def kill(self, *args): + os.kill(self.proc.pid, signal.SIGTERM) + +class SyncRandom(Random): + + def random(self): + # Prior to every generation of randomness we + # fix the seed to be the number of the current + # frame. This is not particularly random, and + # 100% predictable within any given frame, but + # for the time being it's good enough. + Random.seed(self, game.frame_no) + return Random.random(self) + +_random = SyncRandom() + +def choice(*args, **kwargs): + return _random.choice(*args, **kwargs) + +def randint(*args, **kwargs): + return _random.randint(*args, **kwargs) + +def random(*args, **kwargs): + return _random.random(*args, **kwargs) Modified: projectile.py =================================================================== --- projectile.py 2007-02-09 05:56:33 UTC (rev 86) +++ projectile.py 2007-02-09 06:32:45 UTC (rev 87) @@ -9,7 +9,7 @@ import pygame from pygame.locals import * import game -import syncrandom +import netsync from body import RoundBody, everything from vector import Vector2D, fullcircle, halfcircle, diagonal import vessel @@ -114,7 +114,7 @@ partsize = self.partsize * game.camera.zoom radius = self.charge / 2 * game.camera.zoom rects = [self.rect] - bg = syncrandom.randint(0, self.colormax) + bg = netsync.randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg), (self.rect.center, (partsize, partsize))) for i in self.step: @@ -123,7 +123,7 @@ self.rect.centery + direction.y * radius, partsize, partsize) rects.append(part) - bg = syncrandom.randint(0, self.colormax) + bg = netsync.randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg) , part) self.rot += self.partangle self.rot += self.rot_per_frame Deleted: syncclient.py =================================================================== --- syncclient.py 2007-02-09 05:56:33 UTC (rev 86) +++ syncclient.py 2007-02-09 06:32:45 UTC (rev 87) @@ -1,61 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (client) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Network synchronized client -# $Id$ - -import memcache - -class Client: - """Memcached client that magically grows a pool of itself. This - is necessary because the standard client is not threadsafe. - - Note that returning the memcached clients to the pool doesn't - happen when the underlying method raises an exception. This is - intentional, it's how we shelter ourself from curdled connections. - """ - - def __init__(self, *args, **kwargs): - self._pool = [] - self._init_args = args - self._init_kwargs = kwargs - - def _acquire(self): - try: - return self._pool.pop() - except IndexError: - return memcache.Client(*self._init_args, **self._init_kwargs) - - def _release(self, client): - self._pool.append(client) - - def add(self, *args, **kwargs): - client = self._acquire() - result = client.add(*args, **kwargs) - self._release(client) - return result - - def get(self, *args, **kwargs): - client = self._acquire() - result = client.get(*args, **kwargs) - self._release(client) - return result - - def get_multi(self, *args, **kwargs): - client = self._acquire() - result = client.get_multi(*args, **kwargs) - self._release(client) - return result - - def incr(self, *args, **kwargs): - client = self._acquire() - result = client.incr(*args, **kwargs) - self._release(client) - return result - - def set(self, *args, **kwargs): - client = self._acquire() - result = client.set(*args, **kwargs) - self._release(client) - return result Deleted: syncrandom.py =================================================================== --- syncrandom.py 2007-02-09 05:56:33 UTC (rev 86) +++ syncrandom.py 2007-02-09 06:32:45 UTC (rev 87) @@ -1,31 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (client) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Network synchronized randomness -# $Id$ - -import game -from random import Random - -class SyncRandom(Random): - - def random(self): - # Prior to every generation of randomness we - # fix the seed to be the number of the current - # frame. This is not particularly random, and - # 100% predictable within any given frame, but - # for the time being it's good enough. - Random.seed(self, game.frame_no) - return Random.random(self) - -_random = SyncRandom() - -def choice(*args, **kwargs): - return _random.choice(*args, **kwargs) - -def randint(*args, **kwargs): - return _random.randint(*args, **kwargs) - -def random(*args, **kwargs): - return _random.random(*args, **kwargs) Deleted: syncserver.py =================================================================== --- syncserver.py 2007-02-09 05:56:33 UTC (rev 86) +++ syncserver.py 2007-02-09 06:32:45 UTC (rev 87) @@ -1,39 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (client) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Network synchronized server -# $Id$ - -import atexit -import os -import signal -import subprocess -import syncclient - -class Server: - """Wrapper around a memcached server process - """ - - def __init__(self, host='127.0.0.1', port='11211'): - self.host = host - self.port = port - self.proc = subprocess.Popen(['memcached', '-l', self.host, '-p', self.port]) - if not self.isAlive(): - raise RuntimeError, 'memcached server failed to start' - atexit.register(self.kill) - signal.signal(signal.SIGTERM, self.kill) - signal.signal(signal.SIGQUIT, self.kill) - signal.signal(signal.SIGABRT, self.kill) - - def client(self, debug=False): - return syncclient.Client([self.host+':'+self.port], debug=debug) - - def isAlive(self): - return self.proc.returncode is None - - def isReady(self): - return 0 != self.client().add('hello', 'world') - - def kill(self, *args): - os.kill(self.proc.pid, signal.SIGTERM) Deleted: syncstate.py =================================================================== --- syncstate.py 2007-02-09 05:56:33 UTC (rev 86) +++ syncstate.py 2007-02-09 06:32:45 UTC (rev 87) @@ -1,60 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (client) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Network synchronized state -# $Id$ - -import game -import syncclient -import time -from vessel import KeyboardControl - - -_game_id = None -_server = None -_client_id = None -_clients = None -_players = None -def init(host, port, wait, debug=True): - """Establish multiplayer connections - """ - global _server, _game_id, _client_id, _clients, _players - # establish server connection - _server = syncclient.Client(['%s:%s' % (host, port)], debug=debug) - # determine game id - _game_id = 'game=%s' % 666 # FIXME - _server.add(_game_id, '0') - # determine our client id - _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) - # wait for other clients to connect - print 'Waiting for clients...', - time.sleep(wait) - client_count = int(_server.get(_game_id)) - print '%d connected.' % client_count - # sync up with other clients - _clients = ['%s,client=%s' % (_game_id, i+1) - for i in xrange(client_count)] - _players = [c == _client_id and game.player - or KeyboardControl.new_player() - for c in _clients] - - -def send_keystate(frame_no, keystate): - """Send this client's keystate to the server - """ - key = '%s,frame=%d' % (_client_id, frame_no) - _server.add(key, keystate) - - -def step(frame_no): - """Retrieve all keystates from the server - """ - keys = ['%s,frame=%d' % (oc, frame_no) for oc in _clients] - while keys: - results = _server.get_multi(keys) - for key, value in results.items(): - client_id = key.rsplit(',', 1)[0] - index = _clients.index(client_id) - _players[index].control.set_keystate(value) - keys.remove(key) Modified: vessel.py =================================================================== --- vessel.py 2007-02-09 05:56:33 UTC (rev 86) +++ vessel.py 2007-02-09 06:32:45 UTC (rev 87) @@ -10,7 +10,7 @@ import game import math import pygame -import syncrandom +import netsync import sys from pygame.locals import * from media import RotatedImage @@ -117,7 +117,7 @@ self._sys = [] self._damage_sys = [] self.weapons = [] - self.heading = syncrandom.random() * fullcircle + self.heading = netsync.random() * fullcircle @classmethod def load(cls, config_file, **kw): @@ -345,7 +345,7 @@ else: self.system_damage += value while self.system_damage > self.system_damage_threshold: - system = syncrandom.choice(self._sys) + system = netsync.choice(self._sys) system.disable() self.system_damage -= self.system_damage_threshold @@ -480,7 +480,7 @@ image = self.rot_images.rotated( math.radians(game.time / 30)) flicker = int(self.flicker + self.flicker * (self.max_level / self.level)) - flicker = syncrandom.randint(0, flicker) + flicker = netsync.randint(0, flicker) opacity = self.opacity * (self.level / self.max_level / 2) + self.opacity if self.time - game.time <= self.fadeout: opacity *= (self.time - game.time) / self.fadeout This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-09 08:43:22
|
Revision: 91 http://eos-game.svn.sourceforge.net/eos-game/?rev=91&view=rev Author: oberon7 Date: 2007-02-09 00:43:20 -0800 (Fri, 09 Feb 2007) Log Message: ----------- Added command line options for joining a multiplayer game. Modified Paths: -------------- eos.py game.py netsync.py Modified: eos.py =================================================================== --- eos.py 2007-02-09 08:33:08 UTC (rev 90) +++ eos.py 2007-02-09 08:43:20 UTC (rev 91) @@ -11,18 +11,27 @@ if __name__ == '__main__': parser = optparse.OptionParser() + parser.add_option('-j', '--join', + dest='run_server', default=True, action='store_false', + help="join someone else's game") + parser.add_option('-i', '--host', + dest='host', default='127.0.0.1', metavar='HOST', + help='server hostname [default %default]') + parser.add_option('-p', '--port', + dest='port', default='11211', metavar='PORT', + help='server port [default %default]') parser.add_option('--window', dest='fullscreen', default=True, action='store_false', - help='Should we run fullscreen? [default %default]') + help='run in a window') parser.add_option('--profile', dest='do_profile', default=False, action='store_true', - help='Should we enable profiling? [default %default]') - options, args = parser.parse_args() + help='enable profiling') + opts, args = parser.parse_args() if args: raise optparse.OptParseError('Unrecognized args: %s' % args) - game.init(options.fullscreen) + game.init(opts.run_server, opts.host, opts.port, opts.fullscreen) start = pygame.time.get_ticks() - if options.do_profile: + if opts.do_profile: try: import cProfile as profile except ImportError: Modified: game.py =================================================================== --- game.py 2007-02-09 08:33:08 UTC (rev 90) +++ game.py 2007-02-09 08:43:20 UTC (rev 91) @@ -31,7 +31,7 @@ camera = None ai = None -def init(fullscreen=False): +def init(run_server=True, host='127.0.0.1', port='11211', fullscreen=False): import body from sprite import RenderedGroup from stars import StarField @@ -88,10 +88,11 @@ ai.position.x, ai.position.y = netsync.randint(-1000, 1000), netsync.randint(-1000,1000) # Establish networking - server = netsync.Server() - while server.isAlive() and not server.isReady(): - sleep(1) - netsync.init(server.host, server.port, 0) + if run_server: + server = netsync.Server(host, port) + while server.isAlive() and not server.isReady(): + sleep(1) + netsync.init(host, port, 0) def handle_events(): for event in pygame.event.get(): Modified: netsync.py =================================================================== --- netsync.py 2007-02-09 08:33:08 UTC (rev 90) +++ netsync.py 2007-02-09 08:43:20 UTC (rev 91) @@ -21,7 +21,7 @@ _clients = None _players = None -def init(host, port, wait, debug=True): +def init(host, port, wait, debug=False): global _server, _game_id, _client_id, _clients, _players # establish server connection _server = Client(['%s:%s' % (host, port)], debug=debug) @@ -117,7 +117,7 @@ """Wrapper around a memcached server process """ - def __init__(self, host='127.0.0.1', port='11211'): + def __init__(self, host, port): self.host = host self.port = port self.proc = subprocess.Popen(['memcached', '-l', self.host, '-p', self.port]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-09 09:18:54
|
Revision: 92 http://eos-game.svn.sourceforge.net/eos-game/?rev=92&view=rev Author: oberon7 Date: 2007-02-09 01:18:44 -0800 (Fri, 09 Feb 2007) Log Message: ----------- Added player count command line option. In multiplayer games this option is required by all clients in order for them to agree about when the game has filled and is ready to begin. This not ideal but is Good Enough to let us play multiclient games immediately. Modified Paths: -------------- eos.py game.py netsync.py Modified: eos.py =================================================================== --- eos.py 2007-02-09 08:43:20 UTC (rev 91) +++ eos.py 2007-02-09 09:18:44 UTC (rev 92) @@ -14,11 +14,14 @@ parser.add_option('-j', '--join', dest='run_server', default=True, action='store_false', help="join someone else's game") + parser.add_option('-n', '--players', + dest='player_count', default=1, type='int', + help='how many players [default %default]') parser.add_option('-i', '--host', - dest='host', default='127.0.0.1', metavar='HOST', + dest='host', default='127.0.0.1', help='server hostname [default %default]') parser.add_option('-p', '--port', - dest='port', default='11211', metavar='PORT', + dest='port', default='11211', help='server port [default %default]') parser.add_option('--window', dest='fullscreen', default=True, action='store_false', @@ -29,7 +32,7 @@ opts, args = parser.parse_args() if args: raise optparse.OptParseError('Unrecognized args: %s' % args) - game.init(opts.run_server, opts.host, opts.port, opts.fullscreen) + game.init(opts.player_count, opts.run_server, opts.host, opts.port, opts.fullscreen) start = pygame.time.get_ticks() if opts.do_profile: try: Modified: game.py =================================================================== --- game.py 2007-02-09 08:43:20 UTC (rev 91) +++ game.py 2007-02-09 09:18:44 UTC (rev 92) @@ -31,7 +31,7 @@ camera = None ai = None -def init(run_server=True, host='127.0.0.1', port='11211', fullscreen=False): +def init(player_count=1, run_server=True, host='127.0.0.1', port='11211', fullscreen=False): import body from sprite import RenderedGroup from stars import StarField @@ -92,7 +92,7 @@ server = netsync.Server(host, port) while server.isAlive() and not server.isReady(): sleep(1) - netsync.init(host, port, 0) + netsync.init(host, port, player_count) def handle_events(): for event in pygame.event.get(): Modified: netsync.py =================================================================== --- netsync.py 2007-02-09 08:43:20 UTC (rev 91) +++ netsync.py 2007-02-09 09:18:44 UTC (rev 92) @@ -21,7 +21,7 @@ _clients = None _players = None -def init(host, port, wait, debug=False): +def init(host, port, expected_clients, debug=False): global _server, _game_id, _client_id, _clients, _players # establish server connection _server = Client(['%s:%s' % (host, port)], debug=debug) @@ -31,10 +31,14 @@ # determine our client id _client_id = '%s,client=%s' % (_game_id, _server.incr(_game_id)) # wait for other clients to connect - print 'Waiting for clients...', - time.sleep(wait) - client_count = int(_server.get(_game_id)) - print '%d connected.' % client_count + print 'Waiting for clients', + while True: + client_count = int(_server.get(_game_id)) + if client_count >= expected_clients: + break + print '.', + time.sleep(1) + print '%d connected' % client_count # sync up with other clients _clients = ['%s,client=%s' % (_game_id, i+1) for i in xrange(client_count)] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-10 05:14:51
|
Revision: 96 http://eos-game.svn.sourceforge.net/eos-game/?rev=96&view=rev Author: oberon7 Date: 2007-02-09 21:14:49 -0800 (Fri, 09 Feb 2007) Log Message: ----------- Various multiplayer consistency fixes. Modified Paths: -------------- game.py netsync.py Modified: game.py =================================================================== --- game.py 2007-02-09 23:15:22 UTC (rev 95) +++ game.py 2007-02-10 05:14:49 UTC (rev 96) @@ -76,24 +76,24 @@ camera.rect.center = screen.get_rect().center camera.zoom = 1 + # Establish networking + if run_server: + server = netsync.Server(host, port) + while server.isAlive() and not server.isReady(): + sleep(1) + netsync.init(host, port, player_count) + # Create AI for i in range(3): - ai = AIVessel.load('vessels/naree/cress', target=player) + ai = AIVessel.load('vessels/naree/cress', target=netsync.choice(netsync.players)) ai.position.x, ai.position.y = netsync.randint(-1000, 1000), netsync.randint(-1000,1000) for i in range(2): - ai = AIVessel.load('vessels/naree/lotus', target=player) + ai = AIVessel.load('vessels/naree/lotus', target=netsync.choice(netsync.players)) ai.position.x, ai.position.y = netsync.randint(-1000, 1000), netsync.randint(-1000,1000) for i in range(8): - ai = AIVessel.load('vessels/naree/gnat', target=player) + ai = AIVessel.load('vessels/naree/gnat', target=netsync.choice(netsync.players)) ai.position.x, ai.position.y = netsync.randint(-1000, 1000), netsync.randint(-1000,1000) - # Establish networking - if run_server: - server = netsync.Server(host, port) - while server.isAlive() and not server.isReady(): - sleep(1) - netsync.init(host, port, player_count) - def handle_events(): for event in pygame.event.get(): if event.type == QUIT or ( @@ -143,7 +143,8 @@ dirty = sprites.draw(screen) pygame.display.update(dirty) pygame.display.flip() - universe.quickStep(1.0 / avg_fps) + #universe.quickStep(1.0 / avg_fps) # actual FPS + universe.quickStep(1.0 / fps) # target FPS time += clock.tick(fps) avg_fps = clock.get_fps() or fps netsync.step(frame_no) Modified: netsync.py =================================================================== --- netsync.py 2007-02-09 23:15:22 UTC (rev 95) +++ netsync.py 2007-02-10 05:14:49 UTC (rev 96) @@ -19,10 +19,10 @@ _game_id = None _client_id = None _clients = None -_players = None +players = None def init(host, port, expected_clients, debug=False): - global _server, _game_id, _client_id, _clients, _players + global _server, _game_id, _client_id, _clients, players # establish server connection _server = Client(['%s:%s' % (host, port)], debug=debug) # determine game id @@ -42,7 +42,7 @@ # sync up with other clients _clients = ['%s,client=%s' % (_game_id, i+1) for i in xrange(client_count)] - _players = [c == _client_id and game.player + players = [c == _client_id and game.player or vessel.KeyboardControl.new_player() for c in _clients] @@ -61,7 +61,7 @@ for key, value in results.items(): client_id = key.rsplit(',', 1)[0] index = _clients.index(client_id) - _players[index].control.set_keystate(value) + players[index].control.set_keystate(value) keys.remove(key) class Client: @@ -156,12 +156,7 @@ return Random.random(self) _random = SyncRandom() - -def choice(*args, **kwargs): - return _random.choice(*args, **kwargs) - -def randint(*args, **kwargs): - return _random.randint(*args, **kwargs) - -def random(*args, **kwargs): - return _random.random(*args, **kwargs) +choice = _random.choice +randint = _random.randint +choice = _random.choice +random = _random.random This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-02-10 22:38:20
|
Revision: 100 http://eos-game.svn.sourceforge.net/eos-game/?rev=100&view=rev Author: oberon7 Date: 2007-02-10 14:38:17 -0800 (Sat, 10 Feb 2007) Log Message: ----------- Fixed random visual client-side effects to use the standard python random module, rather than the network synchronized one. Modified Paths: -------------- beam.py netsync.py projectile.py vessel.py Modified: beam.py =================================================================== --- beam.py 2007-02-10 22:26:31 UTC (rev 99) +++ beam.py 2007-02-10 22:38:17 UTC (rev 100) @@ -9,7 +9,7 @@ import game import pygame import body -import netsync +import random import vessel from vector import Vector2D @@ -48,7 +48,7 @@ dx *= self.length * camera.zoom dy *= self.length * camera.zoom self.targeted = False - c = netsync.randint(1, 4) + c = random.randint(1, 4) self.rect = pygame.draw.line(surface, (self.color[0]/c, self.color[1]/c, self.color[2]/c), (sx, sy), (sx + dx, sy + dy), self.width) Modified: netsync.py =================================================================== --- netsync.py 2007-02-10 22:26:31 UTC (rev 99) +++ netsync.py 2007-02-10 22:38:17 UTC (rev 100) @@ -160,5 +160,4 @@ _random = SyncRandom() choice = _random.choice randint = _random.randint -choice = _random.choice random = _random.random Modified: projectile.py =================================================================== --- projectile.py 2007-02-10 22:26:31 UTC (rev 99) +++ projectile.py 2007-02-10 22:38:17 UTC (rev 100) @@ -9,7 +9,7 @@ import pygame from pygame.locals import * import game -import netsync +import random from body import RoundBody, everything from vector import Vector2D, fullcircle, halfcircle, diagonal import vessel @@ -114,7 +114,7 @@ partsize = self.partsize * game.camera.zoom radius = self.charge / 2 * game.camera.zoom rects = [self.rect] - bg = netsync.randint(0, self.colormax) + bg = random.randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg), (self.rect.center, (partsize, partsize))) for i in self.step: @@ -123,7 +123,7 @@ self.rect.centery + direction.y * radius, partsize, partsize) rects.append(part) - bg = netsync.randint(0, self.colormax) + bg = random.randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg) , part) self.rot += self.partangle self.rot += self.rot_per_frame Modified: vessel.py =================================================================== --- vessel.py 2007-02-10 22:26:31 UTC (rev 99) +++ vessel.py 2007-02-10 22:38:17 UTC (rev 100) @@ -11,6 +11,7 @@ import math import pygame import netsync +import random import sys from pygame.locals import * from media import RotatedImage @@ -482,7 +483,7 @@ image = self.rot_images.rotated( math.radians(game.time / 30)) flicker = int(self.flicker + self.flicker * (self.max_level / self.level)) - flicker = netsync.randint(0, flicker) + flicker = random.randint(0, flicker) opacity = self.opacity * (self.level / self.max_level / 2) + self.opacity if self.time - game.time <= self.fadeout: opacity *= (self.time - game.time) / self.fadeout This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-02-12 07:21:16
|
Revision: 104 http://eos-game.svn.sourceforge.net/eos-game/?rev=104&view=rev Author: cduncan Date: 2007-02-11 23:20:58 -0800 (Sun, 11 Feb 2007) Log Message: ----------- Preparations for 0.0 release! - script to generate release disk images for MacOS X - html release info packaged with the release - Installation docs to help untangle the dependancies - Improved finder and mac os doc icons Modified Paths: -------------- data/eos.icns game.py Added Paths: ----------- INSTALL RELEASE.html data/eos-icon.png macos/ macos/mkdmg.sh macos/setup.py Removed Paths: ------------- setup.py Added: INSTALL =================================================================== --- INSTALL (rev 0) +++ INSTALL 2007-02-12 07:20:58 UTC (rev 104) @@ -0,0 +1,75 @@ +$Id$ + +Eos Source Installation +======================= + +Although Eos itself is written in interpreted Python, requiring no compilation +itself, it has many dependencies that will need to be installed first: + +- Python 2.5 +- pygame 1.7 or better (Requires SDL) +- pyode (from cvs) (requires ODE 0.8 and pyrex) +- python memcached 1.34 (requires memcached) + +Here are links to the above for convenience. I have found that using a package +management system is the easiest way to get up and running. + +Python 2.5 -- http://www.python.org/download/releases/2.5/ +pygame 1.7 -- http://www.pygame.org/download.shtml +SDL -- http://www.libsdl.org/download-1.2.php +pyode -- cvs -z3 -d:pserver:ano...@py...:/cvsroot/pyode co -P pyode +ODE 0.8 -- http://sourceforge.net/project/showfiles.php?group_id=24884 +pyrex -- http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/ +python memcached 1.34 -- ftp://ftp.tummy.com/pub/python-memcached/ +memcached -- http://www.danga.com/memcached/download.bml + +Verifying installation +---------------------- + +After getting all of the above installed, you can confirm successful +installation from the python command line: + +% python +Python 2.5 (r25:51908, Feb 9 2007, 16:23:18) +[GCC 4.0.1 (Apple Computer, Inc. build 5250)] on darwin +Type "help", "copyright", "credits" or "license" for more information. +>>> import pygame +>>> import ode +>>> import memcache +>>> + +if all three of those imports work without complaints you are good to go! + +Fixing pyode on MacOS X +----------------------- + +If importing ode from python complains about not being able to load +libode.dylib, you can fix it using the install_name_tool utility from +the command line. First find out where the ode.so file is (in your python +site-packages dir). Then find out where your libode.dylib file is (or +libode.0.dylib, whatever the case may be). Then run install_name_tool to +correct the path to libode.dylib in ode.so. + +Below is an example with ODE and Python installed using the fink package +manager (note you will need to be logged in as an admin): + +% sudo install_name_tool -change libode.dylib /sw/lib/libode.0.dylib /sw/lib/python2.5/site-packages/ode.so + +Here is another example with ODE and Python installed using +MacPorts/DarwinPorts: + +% sudo install_name_tool -change libode.dylib /opt/local/lib/libode.dylib /opt/local/lib/python2.4/site-packages/ode.so + +I have reported this issue to the pyode folks, hopefully it can be corrected +in their package. + +Running Eos +----------- + +Once you have all of the dependencies installed, you can run Eos directly from +the source directory. First cd into the eos source dir then run: + +% python eos.py + +There are several command line options that may be useful for debugging. Use +the --help option to list them. Property changes on: INSTALL ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Added: RELEASE.html =================================================================== --- RELEASE.html (rev 0) +++ RELEASE.html 2007-02-12 07:20:58 UTC (rev 104) @@ -0,0 +1,158 @@ +<html> + <head> + <!-- $Id $ --> + <title>Eos -- Dawn of Light: Release Notes</title> + <style type="text/css"> + <!-- + body { border: groove #6666ff 4px; padding: 8px; margin: 0 } + h2 { text-align: center } + h2, h3 { color: #333377 } + h3 { border-bottom: dotted #333377 1px } + th, tt { font-family: monospace } + tt { font-weight: bold } + * { font-family: verdana, arial, helvetica } + --> + </style> + </head> + <body> + <h2>Eos — Dawn of Light<br> + A Space Opera</h2> + <p> + Welcome to the first prevew release of Eos! What you see here represents + a little over a month's effort. Eos was born from a perhaps irrational + love for some classic 2D games, and a desire by the authors to put + their skills to work on something other than backend system + programming. + </p> + <p>Casey Duncan is responsible for the concept of the game (which this + preview release is only a small taste of), the general architecture, + programming and the sprite artwork. Eric Bieschke has lent his talents + to the AI, multiplayer support (coming soon!) and other programming + tasks. Chris Cusson is our resident artist who is busy with character + and concept art to make the game really come to life. Andrew, Abigayle + and Hayden Duncan also deserve mention for their ideas and endless + enthusiasm for testing! + </p> + <img src="http://eos-game.sourceforge.net/images/lotus-128x128.png" + width=128 height=128 align=right hspace=10 vspace=10 + alt="Naree Lotus"> + <p>This preview release gives you a taste of the first-person, arcade + element of the game. The Eos universe will consist of several races, + all with their own space technology. Thus far, only a few ships from a + single race, the technologically advanced Naree, are complete. The + Naree are a highly evolved race whose technology is centered around + energy. You'll notice how their ships appear almost like translucent + glass. In the preview you will be piloting the Lotus, the mid-range + Naree ship. Against you are several other Naree vessels, including + Gnat defense systems, Cress fighters, and other Lotuses like yours. + </p> + <p>The Lotus is armed with a powerful fullerene cannon. The cannon can + be fired rapidly, but is much more effective if allowed to charge + first. You charge the cannon by holding down the fire button + <tt>[Space]</tt> for up to several seconds. The cannon fires when the + button is released. + </p> + + <h3>Controls</h3> + <table border=0> + <tr><th>↑</th><td>Forward thrust</td></tr> + <tr><th>←</th><td>Rotate left</td></tr> + <tr><th>→</th><td>Rotate right</td></tr> + <tr><th>↓</th><td>Reverse maneuvering thrust</td></tr> + <tr><th>[space]</th><td>Fire weapon (hold to charge)</td></tr> + <tr><th>[r shift]</th><td>Show shield status</td></tr> + <tr><th>w</th><td>Forward maneuvering thrust</td></tr> + <tr><th>w + ↑</th><td>Boost (increases top speed)</td></tr> + <tr><th>a</th><td>Left maneuvering thrust</td></tr> + <tr><th>d</th><td>Right maneuvering thrust</td></tr> + <tr><th>s</th><td>Reverse maneuvering thrust (same as ↓)</td></tr> + <tr><th>[esc]</th><td>Quit Eos</td></tr> + </table> + + <h3>To-do List</h3> + <p>Much is left undone in this preview release. We're subscribing to + the "release early, release often" mantra here, so many of these + should be knocked off soon. Note this is nowhere near a complete + list, it just represents some near-term tasks, or things already in + development. + </p> + <ul> + <li>Sound effects and music</li> + <li>Additional planets and moons</li> + <li>Gnats become Lotus weapons instead of standalone ships</li> + <li>Shield "bubbles" should be sized appropriately for ships</li> + <li>Multiplayer support</li> + <li>User-interface for configuring Eos and using multiplayer</li> + <li>More ships and weapons for other races</li> + <li>Improve AI</li> + <li>Character artwork</li> + <li>Friendly escorts</li> + <li>Strategic elements, such as resources, space stations, ability + to build ships</li> + <li>The beginnings of a single-player storylline</li> + </ul> + + <h3>The Shoulders of Giants</h3> + <p>Eos would not exist without the tremendous effort of others pouring + their heart and soul into these amazing open source tools: + </p> + <ul> + <li><a href="http://www.python.org">The Python programming + language</a></li> + <li><a href="http://www.pygame.org/">pygame - python game + development</a></li> + <li><a href="http://www.libsdl.org/">SDL - Simple Directmedia + Layer</a></li> + <li><a href="http://www.ode.org/">ODE - Open Dynamics + Engine</a></li> + <li><a href="http://www.danga.com/memcached/">memcached - + distributed memory object caching system</a></li> + <li><a href="http://www.gimp.org/">GIMP - The GNU Image + Manipulation Program</a></li> + <li>And many other unsung heroes</li> + </ul> + + <h3>License</h3> + <p>Eos itself is released under the open source MIT license. See + <a href="LICENSE.txt">LICENSE.txt</a> for details. Several components + that Eos is built on (pygame, SDL) are released under the + <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a> license. + Eos uses unmodified release versions of these libraries. + </p> + + <h3>Contributing</h3> + <p>Eos is an open source project and as such its success depends + entirely on volunteer effort. Help in just about any form is welcomed, + but we are particularily interested in help with: + </p> + <ul> + <li>Packaging and testing for Linux and Windows</li> + <li>Web/graphics design for an Eos website</li> + <li>Original sprite graphics and artwork</li> + <li>Original sound effects and music</li> + <li>3D modeling, Blender and POV-Ray experience + especially helpful</li> + <li>Python coding</li> + <li>Testing, testing, testing!</li> + </ul> + + <h3>Getting Upgrades and Source Code</h3> + <p>Since Eos is early in development, we intend to do releases + pretty often. You can go to the + <a href="http://sourceforge.net/project/showfiles.php?group_id=186114" + >project download page</a> to find the latest release. There you will + also find the source code. If you are interested in the source code + for use in your own projects (we highly encourage this), the best + place to get it is from the <a href="http://sourceforge.net/svn/?group_id=186114" + >subversion repository</a>. The code there is the latest and greatest, + though we aim to keep it stable and working. + </p> + + <h3>Contacting the Developers</h3> + <p>If you have a suggestion, idea, criticism, find a bug or want to + help out, you can contact the developers via the Eos development + mailing list <a href="mailto:eos...@li..." + >eos...@li...</a>. + </p> + </body> +</html> Property changes on: RELEASE.html ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Added: data/eos-icon.png =================================================================== (Binary files differ) Property changes on: data/eos-icon.png ___________________________________________________________________ Name: svn:mime-type + image/png Modified: data/eos.icns =================================================================== (Binary files differ) Modified: game.py =================================================================== --- game.py 2007-02-11 19:41:58 UTC (rev 103) +++ game.py 2007-02-12 07:20:58 UTC (rev 104) @@ -37,6 +37,7 @@ from staticbody import Planet from vessel import Vessel, KeyboardControl from ai import AIVessel + from media import load_image global universe, collision_space, screen, screen_rect, background global sprites, fps, avg_fps, clock, time, players, camera, ai # Initialize pygame and setup main screen @@ -54,6 +55,7 @@ screen = pygame.display.set_mode(mode, FULLSCREEN, 16) else: screen = pygame.display.set_mode((640, 480)) + pygame.display.set_icon(load_image('eos-icon.png')) screen_rect = screen.get_rect() pygame.display.set_caption('Eos') background = pygame.Surface(screen.get_size()) Added: macos/mkdmg.sh =================================================================== --- macos/mkdmg.sh (rev 0) +++ macos/mkdmg.sh 2007-02-12 07:20:58 UTC (rev 104) @@ -0,0 +1,33 @@ +#!/bin/bash +# +# $Id$ +set -e + +if [ "$1" == "--help" ]; then + echo >&2 " +Build the Eos .app bundle and create a disk image file suitable for release. +Run from the top-level eos directory + +USAGE + + macos/mkdmg.sh VERSION +" + exit 3 +fi + +if [ -z "$1" ]; then + echo "Version number argument not supplied" + exit 1 +fi + +DMG_FILE="macos/Eos-$1.dmg" + +rm -rf macos/dist macos/build ${DMG_FILE} +python2.5 macos/setup.py py2app --bdist-base=macos/build --dist-dir=macos/dist +mv macos/dist/eos.app macos/dist/Eos.app +cp LICENSE.txt macos/dist/ +cp RELEASE.html "macos/dist/readme.html" +hdiutil create -fs HFS+ -srcfolder macos/dist -volname "Eos - Dawn of Light $1" "${DMG_FILE}" +hdiutil attach "${DMG_FILE}" + + Property changes on: macos/mkdmg.sh ___________________________________________________________________ Name: svn:executable + * Name: svn:eol-style + native Copied: macos/setup.py (from rev 97, setup.py) =================================================================== --- macos/setup.py (rev 0) +++ macos/setup.py 2007-02-12 07:20:58 UTC (rev 104) @@ -0,0 +1,23 @@ +""" +Script for generating standalone Mac OS X application +bundle for Eos using py2app + +Usage: + python setup.py py2app + +$Id$ +""" + +from setuptools import setup + +APP = ['eos.py'] +DATA_FILES = ['data', 'vessels'] +OPTIONS = {'argv_emulation': True, + 'iconfile': 'data/eos.icns'} + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) Deleted: setup.py =================================================================== --- setup.py 2007-02-11 19:41:58 UTC (rev 103) +++ setup.py 2007-02-12 07:20:58 UTC (rev 104) @@ -1,23 +0,0 @@ -""" -Script for generating standalone Mac OS X application -bundle for Eos using py2app - -Usage: - python setup.py py2app - -$Id$ -""" - -from setuptools import setup - -APP = ['eos.py'] -DATA_FILES = ['data', 'vessels'] -OPTIONS = {'argv_emulation': True, - 'iconfile': 'data/eos.icns'} - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-06 07:22:44
|
Revision: 127 http://eos-game.svn.sourceforge.net/eos-game/?rev=127&view=rev Author: cduncan Date: 2007-03-05 23:22:42 -0800 (Mon, 05 Mar 2007) Log Message: ----------- - AI now evades fire and backs off when damaged to recharge/cool-down - Radius of plasma shots made bigger to make it possible to hit gnats - Waves now include a warship sooner, with fewer escorts, sometimes all alone Modified Paths: -------------- ai.py game.py projectile.py vessel.py Modified: ai.py =================================================================== --- ai.py 2007-03-06 00:02:12 UTC (rev 126) +++ ai.py 2007-03-06 07:22:42 UTC (rev 127) @@ -33,8 +33,7 @@ self.close_vessels = sprite.Group() self.proximity_radius = self.vessel.radius * 2 self.sensor = sensor - self.steerfunc = netsync.random.choice([self.seek, self.pursue]) - self.steerfunc = self.seek + self.steerfunc = self.pursue def seek_position(self, target_position, predict_ahead=0.75): """Return desired velocity towards a fixed position @@ -52,8 +51,8 @@ return ((target_position - (self.vessel.position + self.vessel.velocity * predict_ahead) ) * game.avg_fps).clamp(self.vessel.max_speed) - def seek(self, predict_ahead=0.25): - """Return desired velocity towards our target + def seek(self): + """Return desired velocity towards a fixed target >>> game.init() >>> target = Vessel() @@ -69,20 +68,11 @@ >>> head == rightangle True """ - velocity = self.vessel.velocity position = self.vessel.position target_position = self.target.sprite.position - target_velocity = self.target.sprite.velocity - predict_ahead = (position + velocity * predict_ahead).distance( - target_position + target_velocity * predict_ahead) / self.vessel.max_speed - approach = (target_position + target_velocity * predict_ahead) - ( - position + velocity * predict_ahead) - new_heading = approach.radians - if approach.length > max(self.target.sprite.radius * 1.5, 100): - return new_heading, approach * game.avg_fps - else: - # Close in, match speed with the target - return (target_position - position).radians, target_velocity.copy() + velocity = (target_position - position) * game.avg_fps + velocity.clamp_ip(self.vessel.max_speed) + return velocity def flee(self): """Return desired velocity away from our target @@ -101,10 +91,9 @@ >>> head == rightangle + halfcircle True """ - heading, velocity = self.seek() - return (heading + halfcircle) % fullcircle, -velocity + velocity = self.seek() + return (velocity.radians + halfcircle) % fullcircle, -velocity - def predict_intercept(self, target, predict_ahead=2): """Return predicted position of target at the time when we should intercept them @@ -139,7 +128,7 @@ else: return target.position - def pursue(self): + def pursue(self, predict_ahead=0.25): """Return desired velocity towards where we predict our target to be @@ -158,10 +147,22 @@ >>> head 0.0 """ - heading = (self.target.sprite.position - self.vessel.position).radians - return heading, self.seek_position(self.predict_intercept(self.target.sprite)) + velocity = self.vessel.velocity + position = self.vessel.position + target_position = self.target.sprite.position + target_velocity = self.target.sprite.velocity + predict_ahead = (position + velocity * predict_ahead).distance( + target_position + target_velocity * predict_ahead) / self.vessel.max_speed + approach = (target_position + target_velocity * predict_ahead) - ( + position + velocity * predict_ahead) + new_heading = approach.radians + if approach.length > max(self.target.sprite.radius * 1.5, 100): + return new_heading, approach * game.avg_fps + else: + # Close in, match speed with the target + return (target_position - position).radians, target_velocity.copy() - def evade(self, target): + def evade(self): """Return desired velocity away from where we predict our target to be. Under evasion, we still turn toward the target, under the assumption that we will resume @@ -182,8 +183,9 @@ >>> head == rightangle True """ - heading, velocity = self.pursue() - return heading, -velocity + seek_velocity = self.seek() + velocity = Vector2D.unit(seek_velocity.radians + diagonal) * self.vessel.max_speed + return seek_velocity.radians, velocity def avoid_vessels(self): """Return a vector away from other nearby vessels to avoid stacking up @@ -201,7 +203,7 @@ self.close_vessels.remove(vessel) if too_close: center /= len(too_close) - return (self.vessel.position - center).normal * self.vessel.velocity.length + return (self.vessel.position - center).normal * self.vessel.max_speed else: return center @@ -220,10 +222,6 @@ self.turn = -1 else: self.turn = 0 - # Only apply thrust if our velocity needs adjustment and we are - # not turning at the full rate. The latter prevents "orbiting" - # targets and make the ship movement look more "natural" - # overshoots tend to correct themselves better this way if self.vessel.velocity != desired_velocity: thrust_dir = (desired_velocity - self.vessel.velocity).radians - self.vessel.heading self.thrust = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal @@ -246,18 +244,27 @@ # Use the sensor to find a target if self.sensor.closest_vessel: self.target.add(self.sensor.closest_vessel) - self.steerfunc = self.seek self.sensor.disable() elif not self.sensor.enabled: self.sensor.enable() if not self.target: - self.steerfunc = self.seek if self.mothership: # head for the mothership self.target.add(self.mothership) else: # No mothership, just head toward a planet self.target.add(game.planets) + elif (self.steerfunc is not self.evade + and isinstance(self.target.sprite, Vessel) + and self.vessel.vessel_class not in ('warship', 'missile') + and self.vessel.health < 0.33 + and self.target.sprite.position.distance(self.vessel.position) < 300): + self.steerfunc = self.flee + elif game.time - self.vessel.damage_time < 3000: + self.steerfunc = self.evade + elif (self.vessel.health > 0.66 + or self.target.sprite.position.distance(self.vessel.position) > 350): + self.steerfunc = self.pursue # steering desired_heading, desired_velocity = self.steerfunc() desired_velocity += self.avoid_vessels() Modified: game.py =================================================================== --- game.py 2007-03-06 00:02:12 UTC (rev 126) +++ game.py 2007-03-06 07:22:42 UTC (rev 127) @@ -144,17 +144,19 @@ position = vector.Vector2D.unit(netsync.random.random() * vector.fullcircle) * 1000 x = target.position.x + position.x y = target.position.y + position.y - if netsync.random.random() * frame_no > 1000: + warship = netsync.random.random() * frame_no > 400 + if warship: ai = AIVessel.load('vessels/naree/lotus', target=target) ai.position.x, ai.position.y = x, y enemies.add(ai) barrier = 200 while 1: + if warship and netsync.random.random() * frame_no < barrier: + break ai = AIVessel.load('vessels/naree/cress', target=target) ai.position.x, ai.position.y = x, y enemies.add(ai) - if netsync.random.random() * frame_no < barrier: - break + warship = True barrier *= 2 next_wave = time + ai_interval * 1000 ai_interval = max(ai_interval * .9, 5) Modified: projectile.py =================================================================== --- projectile.py 2007-03-06 00:02:12 UTC (rev 126) +++ projectile.py 2007-03-06 07:22:42 UTC (rev 127) @@ -165,6 +165,7 @@ Shot( shooter=self.gunmount, image=self.plasma_img.rotated(self.gunmount.heading), + radius=3, damage=self.damage_value, velocity=Vector2D.unit(self.gunmount.heading) * ( 900 - self.damage_value * 10), @@ -197,7 +198,7 @@ """Generic projectile shot""" def __init__(self, shooter, image, damage, velocity, range, - fade_range=None, damage_loss=0): + fade_range=None, damage_loss=0, radius=1): """Generic projectile shot shooter -- body that fired the shot @@ -208,6 +209,7 @@ fade_range -- range when shot image begins to fade (cosmetic) damage_loss -- Reduction in damage at max range """ + self.radius = radius RoundBody.__init__(self, shooter.position, shooter.velocity + velocity, collides_with=everything & ~shooter.category Modified: vessel.py =================================================================== --- vessel.py 2007-03-06 00:02:12 UTC (rev 126) +++ vessel.py 2007-03-06 07:22:42 UTC (rev 127) @@ -121,6 +121,7 @@ self._damage_sys = [] self.weapons = [] self.heading = netsync.random.random() * fullcircle + self.damage_time = 0 @classmethod def load(cls, config_file, **kw): @@ -338,6 +339,7 @@ >>> v.hull_damage 5 """ + self.damage_time = game.time for s in self._damage_sys: value = s.damage(value) if not value: @@ -354,6 +356,12 @@ system.disable() self.system_damage -= self.system_damage_threshold + @property + def health(self): + """Return our health rating between 0 and 1""" + if self._damage_sys: + return sum(system.health for system in self._damage_sys) / len(self._damage_sys) + def kill(self): for system in self._sys: if hasattr(system, 'kill'): @@ -563,6 +571,13 @@ value -= self.level self.level = 0 return value + + @property + def health(self): + if self.max_level: + return self.level / self.max_level + else: + return 0 class Armor(System): @@ -667,6 +682,13 @@ return damage else: return 0 + + @property + def health(self): + if self.durability and self.heat <= self.durability: + return self.durability / self.heat + else: + return 0 class DirectionalThrusters(System): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-15 08:32:39
|
Revision: 135 http://eos-game.svn.sourceforge.net/eos-game/?rev=135&view=rev Author: cduncan Date: 2007-03-15 01:32:37 -0700 (Thu, 15 Mar 2007) Log Message: ----------- Fun maximization: - AI no longer tries to match velocities when close to target, this makes gnats and fighters fly more naturally and less annoyingly - Use actual velocity instead of max velocity when computing ai predict- ahead. It's hard to describe the effect except that it feels "more fun" - AI uses main thrust more often, which seems to make ai fly more realistically. - Don't switch camera to "missile" vessels (i.e., gnats) - Allowing spawning ai ships to select a target for themselves rather than fixing them on the player. This makes dogfights more interesting and fixes some stupid behavior after the player is inevitably killed. The result is quite a bit more fun flying either naree or rone vessels. The ai seems competent but not impossible. gnats are useful for, yet not frustrating against the player. If you build up a big enough posse, the resulting dogfight is still fun to watch even after you're dead. Modified Paths: -------------- ai.py game.py vessel.py Modified: ai.py =================================================================== --- ai.py 2007-03-14 06:07:21 UTC (rev 134) +++ ai.py 2007-03-15 08:32:37 UTC (rev 135) @@ -154,16 +154,17 @@ position = self.vessel.position target_position = self.target.sprite.position target_velocity = self.target.sprite.velocity + if not target_velocity and ( + position.distance(target_position) < self.target.sprite.radius * 1.5): + # close in to stationary target + return self.vessel.heading, target_velocity predict_ahead = (position + velocity * predict_ahead).distance( - target_position + target_velocity * predict_ahead) / self.vessel.max_speed + target_position + target_velocity * predict_ahead) / ( + self.vessel.velocity.length or 1) approach = (target_position + target_velocity * predict_ahead) - ( position + velocity * predict_ahead) new_heading = approach.radians - if approach.length > max(self.target.sprite.radius * 1.5, 100): - return new_heading, approach * game.avg_fps - else: - # Close in, match speed with the target - return (target_position - position).radians, target_velocity.copy() + return new_heading, approach * game.avg_fps def evade(self): """Return desired velocity away from where we predict @@ -227,7 +228,7 @@ self.turn = 0 if self.vessel.velocity != desired_velocity: thrust_dir = (desired_velocity - self.vessel.velocity).radians - self.vessel.heading - self.thrust = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal + self.thrust = thrust_dir < rightangle or thrust_dir > fullcircle - rightangle self.fw_maneuver = not self.thrust and ( thrust_dir < rightangle or thrust_dir > fullcircle - rightangle) self.right_maneuver = halfcircle > thrust_dir + diagonal > rightangle Modified: game.py =================================================================== --- game.py 2007-03-14 06:07:21 UTC (rev 134) +++ game.py 2007-03-15 08:32:37 UTC (rev 135) @@ -147,7 +147,7 @@ y = target.position.y + position.y warship = netsync.random.random() * frame_no > 400 if warship: - ai = AIVessel.load('vessels/naree/lotus', target=target) + ai = AIVessel.load('vessels/naree/lotus') ai.position.x, ai.position.y = x, y enemies.add(ai) if netsync.random.random() * frame_no < 1000: @@ -161,7 +161,7 @@ while 1: if warship and netsync.random.random() * frame_no < barrier: break - ai = AIVessel.load('vessels/naree/cress', target=target) + ai = AIVessel.load('vessels/naree/cress') ai.position.x, ai.position.y = x, y enemies.add(ai) warship = True Modified: vessel.py =================================================================== --- vessel.py 2007-03-14 06:07:21 UTC (rev 134) +++ vessel.py 2007-03-15 08:32:37 UTC (rev 135) @@ -384,7 +384,8 @@ body.RoundBody.kill(self) if game.camera is self: for sprite in game.sprites.sprites(): - if sprite.alive() and isinstance(sprite, Vessel): + if (sprite.alive() and isinstance(sprite, Vessel) + and sprite.vessel_type != 'missile'): game.camera = sprite game.camera.zoom = 1 sprite.off_screen = False This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |