├── NSP ├── NSP-HowProductionWorks.pdf ├── NSP-HowSalesAndMarketingWork.pdf ├── NSP-ImageGuide.pdf ├── NSP-Introduction.pdf ├── NSP-SampleChapter.odt ├── NSP-SampleChapter.pdf ├── NSP-Style Sheet.pdf ├── OOoTemplateInstructions.odt └── nsp-au-template.ott ├── README.md ├── arch.png ├── book_ch01_ex04 └── example.py ├── book_ch02_ex01 └── example.py ├── book_ch02_ex02 └── example.py ├── book_ch02_ex03 └── example.py ├── book_chapter1.example01.py ├── book_chapter1.fodt ├── book_chapter1.odt ├── book_chapter1.odt.html ├── book_chapter1.txt ├── book_chapter2.txt ├── book_chapter3.example01.py ├── book_chapter3.txt ├── ch1.fodt ├── cmdline_example.txt ├── code_examples ├── client.py ├── events.py ├── ex1.py ├── example.py ├── network.py └── server.py ├── conch_snippet.txt ├── diagram-channel.png ├── diagram-gui-cutscene.png ├── diagram-gui-dialog.png ├── diagram-gui-main.png ├── diagram-gui-menu.png ├── diagram-gui-options.png ├── diagram-incoming.png ├── diagram-outgoing.png ├── diagram-serialization-flowchart.png ├── diagram-visualstages.png ├── event_managers └── events.py ├── examples ├── example1.py ├── example1.py.html ├── example2.tar.gz ├── example2 │ ├── client.py │ ├── events.py │ ├── example1.py │ ├── network.py │ └── server.py ├── example3.tar.gz ├── example3 │ ├── client.py │ ├── events.py │ ├── example1.py │ ├── network.py │ └── server.py ├── example4.tar.gz ├── example4 │ ├── client.py │ ├── events.py │ ├── example1.py │ ├── network.py │ └── server.py ├── network_and_menu │ ├── client.py │ ├── events.py │ ├── example1.py │ ├── network.py │ └── server.py ├── server1.py └── server1.py.html ├── fodt_head.txt ├── fodt_tail.txt ├── foolbar.tar.gz ├── game-model.png ├── host_structure.png ├── host_structure.svg ├── interactive_snippet.txt ├── keybd_monitor.png ├── keybd_monitor.svg ├── make.py ├── make_chapter.py ├── mockup.png ├── multi_controller_test.py ├── notes.txt ├── presentation_code_structure.odp ├── pygame_test.py ├── screenshot-example1.png ├── screenshot-example4a.png ├── screenshot-example4b.png ├── server_snippet.txt.html ├── table.html ├── test.fodt ├── test_chapter.odt └── writing-games.html /NSP/NSP-HowProductionWorks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-HowProductionWorks.pdf -------------------------------------------------------------------------------- /NSP/NSP-HowSalesAndMarketingWork.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-HowSalesAndMarketingWork.pdf -------------------------------------------------------------------------------- /NSP/NSP-ImageGuide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-ImageGuide.pdf -------------------------------------------------------------------------------- /NSP/NSP-Introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-Introduction.pdf -------------------------------------------------------------------------------- /NSP/NSP-SampleChapter.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-SampleChapter.odt -------------------------------------------------------------------------------- /NSP/NSP-SampleChapter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-SampleChapter.pdf -------------------------------------------------------------------------------- /NSP/NSP-Style Sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/NSP-Style Sheet.pdf -------------------------------------------------------------------------------- /NSP/OOoTemplateInstructions.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/OOoTemplateInstructions.odt -------------------------------------------------------------------------------- /NSP/nsp-au-template.ott: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/NSP/nsp-au-template.ott -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # writing_games_tutorial 2 | sjbrown's Writing Games Tutorial - writing games with Python, Pygame, Twisted 3 | 4 | Published here: 5 | http://ezide.com/games/writing-games.html 6 | 7 | I hope that you find this tutorial helpful. 8 | -------------------------------------------------------------------------------- /arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/arch.png -------------------------------------------------------------------------------- /book_ch01_ex04/example.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pygame 3 | import pygame.constants as c 4 | 5 | score = 0 6 | 7 | screenDimensions = pygame.Rect((0,0,400,100)) 8 | 9 | black = (0,0,0) 10 | white = (255,255,255) 11 | blue = (0,0,255) 12 | red = (255,0,0) 13 | 14 | class Monkey(pygame.sprite.Sprite): 15 | def __init__(self): 16 | self.stunTimeout = None 17 | self.velocity = 2 18 | super(Monkey, self).__init__() 19 | self.image = pygame.Surface((60,60)) 20 | self.rect = self.image.get_rect() 21 | self.render(blue) 22 | 23 | def render(self, color): 24 | '''draw onto self.image the face of a monkey in the specified color''' 25 | self.image.fill(color) 26 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 27 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 28 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 29 | 30 | def attempt_punch(self, pos): 31 | '''If the given position (pos) is inside the monkey's rect, the monkey 32 | has been "punched". A successful punch will stun the monkey and 33 | increment the global score. 34 | The monkey cannot be punched if he is already stunned 35 | ''' 36 | if self.stunTimeout: 37 | return # already stunned 38 | if self.rect.collidepoint(pos): 39 | # Argh! The punch intersected with my face! 40 | self.stunTimeout = time.time() + 2 # 2 seconds from now 41 | global score 42 | score += 1 43 | self.render(red) 44 | 45 | def update(self): 46 | if self.stunTimeout: 47 | # If stunned, the monkey doesn't move 48 | if time.time() > self.stunTimeout: 49 | self.stunTimeout = None 50 | self.render(blue) 51 | else: 52 | # Move the monkey 53 | self.rect.x += self.velocity 54 | # Don't let the monkey run past the edge of the viewable area 55 | if self.rect.right > screenDimensions.right: 56 | self.velocity = -2 57 | elif self.rect.left < screenDimensions.left: 58 | self.velocity = 2 59 | 60 | 61 | sprites = pygame.sprite.Group() 62 | 63 | def init(): 64 | # Necessary Pygame set-up... 65 | pygame.init() 66 | clock = pygame.time.Clock() 67 | displayImg = pygame.display.set_mode(screenDimensions.size) 68 | monkey = Monkey() 69 | sprites.add(monkey) 70 | 71 | return (clock, displayImg) 72 | 73 | def handle_events(clock): 74 | for event in pygame.event.get(): 75 | if event.type == c.QUIT: 76 | return False 77 | elif event.type == c.MOUSEBUTTONDOWN: 78 | for sprite in sprites: 79 | if isinstance(sprite, Monkey): 80 | sprite.attempt_punch(event.pos) 81 | 82 | clock.tick(60) # aim for 60 frames per second 83 | for sprite in sprites: 84 | sprite.update() 85 | 86 | return True 87 | 88 | def draw_to_display(displayImg): 89 | displayImg.fill(black) 90 | for sprite in sprites: 91 | displayImg.blit(sprite.image, sprite.rect) 92 | pygame.display.flip() 93 | 94 | def main(): 95 | clock, displayImg = init() 96 | 97 | keepGoing = True 98 | 99 | while keepGoing: 100 | keepGoing = handle_events(clock) 101 | draw_to_display(displayImg) 102 | 103 | if __name__ == '__main__': 104 | main() 105 | print 'Your score was', score 106 | -------------------------------------------------------------------------------- /book_ch02_ex01/example.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pygame 3 | import pygame.constants as c 4 | 5 | score = 0 6 | 7 | screenDimensions = pygame.Rect((0,0,400,100)) 8 | 9 | black = (0,0,0) 10 | white = (255,255,255) 11 | blue = (0,0,255) 12 | red = (255,0,0) 13 | 14 | class Monkey(pygame.sprite.Sprite): 15 | def __init__(self): 16 | self.stunTimeout = None 17 | self.velocity = 2 18 | super(Monkey, self).__init__() 19 | self.image = pygame.Surface((60,60)) 20 | self.rect = self.image.get_rect() 21 | self.render(blue) 22 | 23 | def render(self, color): 24 | '''draw onto self.image the face of a monkey in the specified color''' 25 | self.image.fill(color) 26 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 27 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 28 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 29 | 30 | def attempt_punch(self, pos): 31 | '''If the given position (pos) is inside the monkey's rect, the monkey 32 | has been "punched". A successful punch will stun the monkey and 33 | increment the global score. 34 | The monkey cannot be punched if he is already stunned 35 | ''' 36 | if self.stunTimeout: 37 | return # already stunned 38 | if self.rect.collidepoint(pos): 39 | # Argh! The punch intersected with my face! 40 | self.stunTimeout = time.time() + 2 # 2 seconds from now 41 | global score 42 | score += 1 43 | self.render(red) 44 | 45 | def update(self): 46 | if self.stunTimeout: 47 | # If stunned, the monkey doesn't move 48 | if time.time() > self.stunTimeout: 49 | self.stunTimeout = None 50 | self.render(blue) 51 | else: 52 | # Move the monkey 53 | self.rect.x += self.velocity 54 | # Don't let the monkey run past the edge of the viewable area 55 | if self.rect.right > screenDimensions.right: 56 | self.velocity = -2 57 | elif self.rect.left < screenDimensions.left: 58 | self.velocity = 2 59 | 60 | def do_special(self): 61 | print 'monkey does special' 62 | 63 | class Trap(pygame.sprite.Sprite): 64 | def __init__(self): 65 | self.image = pygame.Surface((20,20)) 66 | self.rect = self.image.get_rect() 67 | self.render(red) 68 | 69 | def render(self, color): 70 | '''draw onto self.image the face of a monkey in the specified color''' 71 | self.image.fill(color) 72 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 73 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 74 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 75 | 76 | def do_special(self): 77 | print 'trap does special' 78 | 79 | sprites = pygame.sprite.Group() 80 | 81 | def init(): 82 | # Necessary Pygame set-up... 83 | pygame.init() 84 | clock = pygame.time.Clock() 85 | displayImg = pygame.display.set_mode(screenDimensions.size) 86 | monkey = Monkey() 87 | sprites.add(monkey) 88 | 89 | return (clock, displayImg) 90 | 91 | def some_other_events(): 92 | return [] 93 | 94 | def from_somewhere(): 95 | return "yeah, this is pretty special alright" 96 | 97 | def generate_events(clock): 98 | for event in pygame.event.get(): 99 | yield event 100 | 101 | clock.tick(60) # aim for 60 frames per second 102 | yield 'ClockTick' 103 | 104 | for event in some_other_events(): 105 | yield event 106 | 107 | specialEvent = from_somewhere() 108 | yield 'SpecialEvent' 109 | 110 | event_type_B, event_type_C, event_type_D = 1,2,3 111 | 112 | def handle_events(clock): 113 | for event in generate_events(clock): 114 | if event == 'ClockTick': 115 | for sprite in sprites: 116 | sprite.update() 117 | elif event == 'SpecialEvent': 118 | for sprite in sprites: 119 | sprite.do_special() 120 | # handle those events that came from some_other_events() 121 | elif event.type == c.QUIT: 122 | return False 123 | elif event.type == c.MOUSEBUTTONDOWN: 124 | for sprite in sprites: 125 | if isinstance(sprite, Monkey): 126 | sprite.attempt_punch(event.pos) 127 | elif event.type == event_type_B: 128 | for sprite in sprites: 129 | if isinstance(sprite, Trap): 130 | pass 131 | elif event.type == event_type_C: 132 | pass 133 | elif event.type == event_type_D: 134 | pass 135 | return True 136 | 137 | def draw_to_display(displayImg): 138 | displayImg.fill(black) 139 | for sprite in sprites: 140 | displayImg.blit(sprite.image, sprite.rect) 141 | pygame.display.flip() 142 | 143 | def main(): 144 | clock, displayImg = init() 145 | 146 | keepGoing = True 147 | 148 | while keepGoing: 149 | keepGoing = handle_events(clock) 150 | draw_to_display(displayImg) 151 | 152 | if __name__ == '__main__': 153 | main() 154 | print 'Your score was', score 155 | -------------------------------------------------------------------------------- /book_ch02_ex02/example.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pygame 3 | import pygame.constants as c 4 | 5 | score = 0 6 | 7 | screenDimensions = pygame.Rect((0,0,400,100)) 8 | 9 | black = (0,0,0) 10 | white = (255,255,255) 11 | blue = (0,0,255) 12 | red = (255,0,0) 13 | 14 | class Monkey(pygame.sprite.Sprite): 15 | def __init__(self): 16 | self.stunTimeout = None 17 | self.velocity = 2 18 | super(Monkey, self).__init__() 19 | self.image = pygame.Surface((60,60)) 20 | self.rect = self.image.get_rect() 21 | self.render(blue) 22 | 23 | def render(self, color): 24 | '''draw onto self.image the face of a monkey in the specified color''' 25 | self.image.fill(color) 26 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 27 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 28 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 29 | 30 | def attempt_punch(self, pos): 31 | '''If the given position (pos) is inside the monkey's rect, the monkey 32 | has been "punched". A successful punch will stun the monkey and 33 | increment the global score. 34 | The monkey cannot be punched if he is already stunned 35 | ''' 36 | if self.stunTimeout: 37 | return # already stunned 38 | if self.rect.collidepoint(pos): 39 | # Argh! The punch intersected with my face! 40 | self.stunTimeout = time.time() + 2 # 2 seconds from now 41 | global score 42 | score += 1 43 | self.render(red) 44 | 45 | def update(self): 46 | if self.stunTimeout: 47 | # If stunned, the monkey doesn't move 48 | if time.time() > self.stunTimeout: 49 | self.stunTimeout = None 50 | self.render(blue) 51 | else: 52 | # Move the monkey 53 | self.rect.x += self.velocity 54 | # Don't let the monkey run past the edge of the viewable area 55 | if self.rect.right > screenDimensions.right: 56 | self.velocity = -2 57 | elif self.rect.left < screenDimensions.left: 58 | self.velocity = 2 59 | 60 | def do_special(self): 61 | print 'monkey does special' 62 | 63 | def on_event(self, event): 64 | if event == 'ClockTick': 65 | self.update() 66 | elif event == 'SpecialEvent': 67 | self.do_special() 68 | elif event.type == c.MOUSEBUTTONDOWN: 69 | self.attempt_punch(event.pos) 70 | # notice that Monkey doesn't do anything on event_type_B 71 | elif event.type == event_type_C: 72 | pass 73 | elif event.type == event_type_D: 74 | pass 75 | 76 | 77 | class Trap(pygame.sprite.Sprite): 78 | def __init__(self): 79 | self.image = pygame.Surface((20,20)) 80 | self.rect = self.image.get_rect() 81 | self.render(red) 82 | 83 | def render(self, color): 84 | '''draw onto self.image the face of a monkey in the specified color''' 85 | self.image.fill(color) 86 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 87 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 88 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 89 | 90 | def do_special(self): 91 | print 'trap does special' 92 | def add_some_honey(self): 93 | print 'trap adds honey' 94 | 95 | def on_event(self, event): 96 | if event == 'ClockTick': 97 | self.update() 98 | elif event == 'SpecialEvent': 99 | self.do_special() 100 | # notice that Trap doesn't do anything on MOUSEBUTTONDOWN 101 | elif event.type == event_type_B: 102 | self.add_some_honey() 103 | elif event.type == event_type_C: 104 | pass 105 | elif event.type == event_type_D: 106 | pass 107 | 108 | 109 | sprites = pygame.sprite.Group() 110 | 111 | def init(): 112 | # Necessary Pygame set-up... 113 | pygame.init() 114 | clock = pygame.time.Clock() 115 | displayImg = pygame.display.set_mode(screenDimensions.size) 116 | monkey = Monkey() 117 | sprites.add(monkey) 118 | 119 | return (clock, displayImg) 120 | 121 | def some_other_events(): 122 | return [] 123 | 124 | def from_somewhere(): 125 | return "yeah, this is pretty special alright" 126 | 127 | def generate_events(clock): 128 | for event in pygame.event.get(): 129 | yield event 130 | 131 | clock.tick(60) # aim for 60 frames per second 132 | yield 'ClockTick' 133 | 134 | for event in some_other_events(): 135 | yield event 136 | 137 | specialEvent = from_somewhere() 138 | yield 'SpecialEvent' 139 | 140 | event_type_B, event_type_C, event_type_D = 1,2,3 141 | 142 | def handle_events(clock): 143 | for event in generate_events(clock): 144 | if hasattr(event, 'type') and event.type == c.QUIT: 145 | return False 146 | for sprite in sprites: 147 | sprite.on_event(event) 148 | return True 149 | 150 | def draw_to_display(displayImg): 151 | displayImg.fill(black) 152 | for sprite in sprites: 153 | displayImg.blit(sprite.image, sprite.rect) 154 | pygame.display.flip() 155 | 156 | def main(): 157 | clock, displayImg = init() 158 | 159 | keepGoing = True 160 | 161 | while keepGoing: 162 | keepGoing = handle_events(clock) 163 | draw_to_display(displayImg) 164 | 165 | if __name__ == '__main__': 166 | main() 167 | print 'Your score was', score 168 | -------------------------------------------------------------------------------- /book_ch02_ex03/example.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pygame 3 | import pygame.constants as c 4 | 5 | score = 0 6 | 7 | screenDimensions = pygame.Rect((0,0,400,100)) 8 | 9 | black = (0,0,0) 10 | white = (255,255,255) 11 | blue = (0,0,255) 12 | red = (255,0,0) 13 | 14 | class EventHandlingSprite(pygame.sprite.Sprite): 15 | def on_ClockTick(self): 16 | self.update() 17 | def on_Special(self): 18 | 'sprite got special' 19 | 20 | 21 | class Monkey(EventHandlingSprite): 22 | def __init__(self): 23 | self.stunTimeout = None 24 | self.velocity = 2 25 | super(Monkey, self).__init__() 26 | self.image = pygame.Surface((60,60)) 27 | self.rect = self.image.get_rect() 28 | self.render(blue) 29 | 30 | def render(self, color): 31 | '''draw onto self.image the face of a monkey in the specified color''' 32 | self.image.fill(color) 33 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 34 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 35 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 36 | 37 | def attempt_punch(self, pos): 38 | '''If the given position (pos) is inside the monkey's rect, the monkey 39 | has been "punched". A successful punch will stun the monkey and 40 | increment the global score. 41 | The monkey cannot be punched if he is already stunned 42 | ''' 43 | if self.stunTimeout: 44 | return # already stunned 45 | if self.rect.collidepoint(pos): 46 | # Argh! The punch intersected with my face! 47 | self.stunTimeout = time.time() + 2 # 2 seconds from now 48 | global score 49 | score += 1 50 | self.render(red) 51 | 52 | def update(self): 53 | if self.stunTimeout: 54 | # If stunned, the monkey doesn't move 55 | if time.time() > self.stunTimeout: 56 | self.stunTimeout = None 57 | self.render(blue) 58 | else: 59 | # Move the monkey 60 | self.rect.x += self.velocity 61 | # Don't let the monkey run past the edge of the viewable area 62 | if self.rect.right > screenDimensions.right: 63 | self.velocity = -2 64 | elif self.rect.left < screenDimensions.left: 65 | self.velocity = 2 66 | 67 | def on_PygameEvent(self, event): 68 | if event.type == c.MOUSEBUTTONDOWN: 69 | self.attempt_punch(event.pos) 70 | elif event.type == event_type_C: 71 | pass 72 | elif event.type == event_type_D: 73 | pass 74 | 75 | 76 | class Trap(EventHandlingSprite): 77 | def __init__(self): 78 | self.image = pygame.Surface((20,20)) 79 | self.rect = self.image.get_rect() 80 | self.render(red) 81 | 82 | def render(self, color): 83 | '''draw onto self.image the face of a monkey in the specified color''' 84 | self.image.fill(color) 85 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 86 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 87 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 88 | 89 | def add_some_honey(self): 90 | print 'trap adds honey' 91 | 92 | def on_PygameEvent(self, event): 93 | if event.type == event_type_B: 94 | self.add_some_honey() 95 | elif event.type == event_type_C: 96 | pass 97 | elif event.type == event_type_D: 98 | pass 99 | 100 | sprites = pygame.sprite.Group() 101 | 102 | def init(): 103 | # Necessary Pygame set-up... 104 | pygame.init() 105 | clock = pygame.time.Clock() 106 | displayImg = pygame.display.set_mode(screenDimensions.size) 107 | monkey = Monkey() 108 | sprites.add(monkey) 109 | 110 | return (clock, displayImg) 111 | 112 | def some_other_events(): 113 | return [] 114 | 115 | def from_somewhere(): 116 | return "yeah, this is pretty special alright" 117 | 118 | def generate_events(clock): 119 | for event in pygame.event.get(): 120 | yield ('PygameEvent', event) 121 | 122 | clock.tick(60) # aim for 60 frames per second 123 | yield ('ClockTick', ) 124 | 125 | for event in some_other_events(): 126 | yield (event.__class__.__name__, event) 127 | 128 | specialEvent = from_somewhere() 129 | yield ('SpecialEvent', ) 130 | 131 | event_type_B, event_type_C, event_type_D = 1,2,3 132 | 133 | def handle_events(clock): 134 | for eventTuple in generate_events(clock): 135 | if eventTuple[0] == 'PygameEvent' and eventTuple[1].type == c.QUIT: 136 | return False 137 | for sprite in sprites: 138 | methodName = 'on_' + eventTuple[0] 139 | if hasattr(sprite, methodName): 140 | method = getattr(sprite, methodName) 141 | method(*eventTuple[1:]) 142 | return True 143 | 144 | def draw_to_display(displayImg): 145 | displayImg.fill(black) 146 | for sprite in sprites: 147 | displayImg.blit(sprite.image, sprite.rect) 148 | pygame.display.flip() 149 | 150 | def main(): 151 | clock, displayImg = init() 152 | 153 | keepGoing = True 154 | 155 | while keepGoing: 156 | keepGoing = handle_events(clock) 157 | draw_to_display(displayImg) 158 | 159 | if __name__ == '__main__': 160 | main() 161 | print 'Your score was', score 162 | -------------------------------------------------------------------------------- /book_chapter1.example01.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pygame 3 | import pygame.constants as c 4 | 5 | score = 0 6 | 7 | screenDimensions = pygame.Rect((0,0,400,100)) 8 | 9 | black = (0,0,0) 10 | white = (255,255,255) 11 | blue = (0,0,255) 12 | red = (255,0,0) 13 | 14 | class Monkey(pygame.sprite.Sprite): 15 | def __init__(self): 16 | self.stunTimeout = None 17 | self.velocity = 2 18 | super(Monkey, self).__init__() 19 | self.image = pygame.Surface((60,60)) 20 | self.rect = self.image.get_rect() 21 | self.render(blue) 22 | 23 | def render(self, color): 24 | '''draw onto self.image the face of a monkey in the specified color''' 25 | self.image.fill(color) 26 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 27 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 28 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 29 | 30 | def attempt_punch(self, pos): 31 | '''If the given position (pos) is inside the monkey's rect, the monkey 32 | has been "punched". A successful punch will stun the monkey and increment 33 | the global score. The monkey cannot be punched if he is already stunned 34 | ''' 35 | if self.stunTimeout: 36 | return # already stunned 37 | if self.rect.collidepoint(pos): 38 | # Argh! The punch intersected with my face! 39 | self.stunTimeout = time.time() + 2 # 2 seconds from now 40 | global score 41 | score += 1 42 | self.render(red) 43 | 44 | def update(self): 45 | if self.stunTimeout: 46 | # If stunned, the monkey doesn't move 47 | if time.time() > self.stunTimeout: 48 | self.stunTimeout = None 49 | self.render(blue) 50 | else: 51 | # Move the monkey 52 | self.rect.x += self.velocity 53 | # Don't let the monkey run past the edge of the viewable area 54 | if self.rect.right > screenDimensions.right: 55 | self.velocity = -2 56 | elif self.rect.left < screenDimensions.left: 57 | self.velocity = 2 58 | 59 | 60 | def main(): 61 | pygame.init() 62 | clock = pygame.time.Clock() 63 | displayImg = pygame.display.set_mode(screenDimensions.size) 64 | monkey = Monkey() 65 | 66 | while True: 67 | for event in pygame.event.get(): 68 | if event.type == c.QUIT: 69 | return 70 | elif event.type == c.MOUSEBUTTONDOWN: 71 | monkey.attempt_punch(event.pos) 72 | 73 | clock.tick(60) # aim for 60 FPS 74 | monkey.update() 75 | 76 | displayImg.fill(black) 77 | displayImg.blit(monkey.image, monkey.rect) 78 | pygame.display.flip() 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | print 'Your score was', score 84 | -------------------------------------------------------------------------------- /book_chapter1.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/book_chapter1.odt -------------------------------------------------------------------------------- /book_chapter3.example01.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | import pygame 4 | import pygame.constants as c 5 | 6 | score = 0 7 | 8 | screenDimensions = pygame.Rect((0,0,400,60)) 9 | 10 | sprites = pygame.sprite.Group() 11 | 12 | black = (0,0,0) 13 | white = (255,255,255) 14 | blue = (0,0,255) 15 | red = (255,0,0) 16 | 17 | class Monkey(pygame.sprite.Sprite): 18 | def __init__(self): 19 | self.stunTimeout = None 20 | self.origVelocity = 2 21 | self.velocity = 2 22 | super(Monkey, self).__init__() 23 | self.image = pygame.Surface((60,60)) 24 | self.rect = self.image.get_rect() 25 | self.render(blue) 26 | 27 | def render(self, color): 28 | '''draw onto self.image the face of a monkey in the specified color''' 29 | self.image.fill(color) 30 | pygame.draw.circle(self.image, white, (10,10), 10, 2) 31 | pygame.draw.circle(self.image, white, (50,10), 10, 2) 32 | pygame.draw.circle(self.image, white, (30,60), 20, 2) 33 | 34 | def attempt_punch(self, pos): 35 | '''If the given position (pos) is inside the monkey's rect, the monkey 36 | has been "punched". A successful punch will stun the monkey and increment 37 | the global score. The monkey cannot be punched if he is already stunned 38 | ''' 39 | if self.stunTimeout: 40 | return # already stunned 41 | if self.rect.collidepoint(pos): 42 | # Argh! The punch intersected with my face! 43 | self.stunTimeout = time.time() + 2 # 2 seconds from now 44 | global score 45 | score += 1 46 | self.render(red) 47 | 48 | def adjust_speed(self, multiplier): 49 | if self.velocity > 0: 50 | self.velocity = multiplier * self.origVelocity 51 | if self.velocity < 0: 52 | self.velocity = multiplier * -self.origVelocity 53 | 54 | def update(self): 55 | if self.stunTimeout: 56 | # If stunned, the monkey doesn't move 57 | if time.time() > self.stunTimeout: 58 | self.stunTimeout = None 59 | self.render(blue) 60 | else: 61 | # Move the monkey 62 | self.rect.x += self.velocity 63 | # Don't let the monkey run past the edge of the viewable area 64 | if (self.rect.right > screenDimensions.right or 65 | self.rect.left < screenDimensions.left): 66 | self.velocity = -self.velocity 67 | 68 | 69 | def init(): 70 | # Necessary Pygame set-up... 71 | pygame.init() 72 | clock = pygame.time.Clock() 73 | displayImg = pygame.display.set_mode(screenDimensions.size) 74 | monkey = Monkey() 75 | sprites.add(monkey) 76 | 77 | return (clock, displayImg) 78 | 79 | def get_opponent_score(): 80 | time.sleep(random.random()) 81 | return score # just for pretend 82 | 83 | def handle_events(clock): 84 | monkeys = [] 85 | for sprite in sprites: 86 | if isinstance(sprite, Monkey): 87 | monkeys.append(sprite) 88 | 89 | for event in pygame.event.get(): 90 | if event.type == c.QUIT: 91 | return False 92 | elif event.type == c.MOUSEBUTTONDOWN: 93 | for monkey in monkeys: 94 | monkey.attempt_punch(event.pos) 95 | 96 | opponentScore = get_opponent_score() 97 | difference = opponentScore - score 98 | if difference > 0: 99 | multiplier = 1.0 + difference/10.0 100 | else: 101 | multiplier = 1.0 102 | for monkey in monkeys: 103 | monkey.adjust_speed(multiplier) 104 | 105 | clock.tick(60) # aim for 60 frames per second 106 | for sprite in sprites: 107 | sprite.update() 108 | 109 | return True 110 | 111 | def draw_to_display(displayImg): 112 | displayImg.fill(black) 113 | for sprite in sprites: 114 | displayImg.blit(sprite.image, sprite.rect) 115 | pygame.display.flip() 116 | 117 | def main(): 118 | clock, displayImg = init() 119 | 120 | keepGoing = True 121 | 122 | while keepGoing: 123 | keepGoing = handle_events(clock) 124 | draw_to_display(displayImg) 125 | 126 | if __name__ == '__main__': 127 | main() 128 | -------------------------------------------------------------------------------- /book_chapter3.txt: -------------------------------------------------------------------------------- 1 | Ok, now lets look at what will happen when we add some networking code. 2 | We're writing a multiplayer game after all. 3 | 4 | Imagine this was a 2-player game, opponents are connected to each other over 5 | the internet, and when your opponent has a higher score than you, it makes 6 | your monkey move faster. 7 | 8 | It might look something like this: 9 | 10 | ---- 11 | 12 | def handle_events(clock): 13 | monkeys = [] 14 | for sprite in sprites: 15 | if isinstance(sprite, Monkey): 16 | monkeys.append(sprite) 17 | 18 | for event in pygame.event.get(): 19 | if event.type == c.QUIT: 20 | return False 21 | elif event.type == c.MOUSEBUTTONDOWN: 22 | for monkey in monkeys: 23 | monkey.attempt_punch(event.pos) 24 | 25 | opponentScore = get_opponent_score() 26 | difference = opponentScore - score 27 | if difference > 0: 28 | multiplier = 1.0 + difference/10.0 29 | else: 30 | multiplier = 1.0 31 | for monkey in monkeys: 32 | monkey.adjust_speed(multiplier) 33 | 34 | clock.tick(60) # aim for 60 frames per second 35 | for sprite in sprites: 36 | sprite.update() 37 | 38 | return True 39 | 40 | ---- 41 | 42 | This won't work. An important fact to understand about sending and receiving 43 | information over the network is that the network is slow. (Even "high speed" 44 | networks still experience latency) Our screen won't be 45 | redrawn every 1/60th of a second if it takes get_opponent_score() 2 seconds to 46 | make a connection and pull down an integer from a remote host. 47 | 48 | As proof, try to run this example with the networking functions simulated by 49 | a sleep() of between 0 and 1 seconds. 50 | 51 | ---- 52 | ---- 53 | 54 | As you can see, the monkey randomly jerks his way across the screen. Bad monkey. 55 | 56 | What do we do about these two opposing needs? The display needs to be updated 57 | many times per second, but networking calls can take seconds to return. 58 | -------------------------------------------------------------------------------- /cmdline_example.txt: -------------------------------------------------------------------------------- 1 | from twisted.spread import pb 2 | from twisted.internet import reactor 3 | factory = pb.PBClientFactory() 4 | server = None 5 | def gotServer(serv): 6 | global server 7 | server = serv 8 | reactor.stop() 9 | 10 | connection = reactor.connectTCP('localhost', 8000, factory) 11 | d = factory.getRootObject() 12 | d.addCallback(gotServer) 13 | # 14 | reactor.run() 15 | print server 16 | # 17 | 18 | -------------------------------------------------------------------------------- /code_examples/events.py: -------------------------------------------------------------------------------- 1 | #SECURITY NOTE: anything in here can be created simply by sending the 2 | # class name over the network. This is a potential vulnerability 3 | # I wouldn't suggest letting any of these classes DO anything, especially 4 | # things like file system access, or allocating huge amounts of memory 5 | 6 | class Event: 7 | """this is a superclass for any events that might be generated by an 8 | object and sent to the EventManager""" 9 | def __init__(self): 10 | self.name = "Generic Event" 11 | def __str__(self): 12 | return '<%s %s>' % (self.__class__.__name__, 13 | id(self)) 14 | 15 | 16 | class TickEvent(Event): 17 | def __init__(self): 18 | self.name = "CPU Tick Event" 19 | 20 | class SecondEvent(Event): 21 | def __init__(self): 22 | self.name = "Clock One Second Event" 23 | 24 | class QuitEvent(Event): 25 | def __init__(self): 26 | self.name = "Program Quit Event" 27 | 28 | class FatalEvent(Event): 29 | def __init__(self, *args): 30 | self.name = "Fatal Error Event" 31 | self.args = args 32 | 33 | class MapBuiltEvent(Event): 34 | def __init__(self, map): 35 | self.name = "Map Finished Building Event" 36 | self.map = map 37 | 38 | class GameStartRequest(Event): 39 | def __init__(self): 40 | self.name = "Game Start Request" 41 | 42 | class GameStartedEvent(Event): 43 | def __init__(self, game): 44 | self.name = "Game Started Event" 45 | self.game = game 46 | 47 | class CharactorMoveRequest(Event): 48 | def __init__(self, player, charactor, direction): 49 | self.name = "Charactor Move Request" 50 | self.player = player 51 | self.charactor = charactor 52 | self.direction = direction 53 | 54 | class CharactorMoveEvent(Event): 55 | def __init__(self, charactor): 56 | self.name = "Charactor Move Event" 57 | self.charactor = charactor 58 | 59 | class CharactorPlaceEvent(Event): 60 | """this event occurs when a Charactor is *placed* in a sector, 61 | ie it doesn't move there from an adjacent sector.""" 62 | def __init__(self, charactor): 63 | self.name = "Charactor Placement Event" 64 | self.charactor = charactor 65 | 66 | class ServerConnectEvent(Event): 67 | """the client generates this when it detects that it has successfully 68 | connected to the server""" 69 | def __init__(self, serverReference): 70 | self.name = "Network Server Connection Event" 71 | self.server = serverReference 72 | 73 | class ClientConnectEvent(Event): 74 | """this event is generated by the Server whenever a client connects 75 | to it""" 76 | def __init__(self, client, avatarID): 77 | self.name = "Network Client Connection Event" 78 | self.client = client 79 | self.avatarID = avatarID 80 | 81 | class ClientDisconnectEvent(Event): 82 | """this event is generated by the Server when it finds that a client 83 | is no longer connected""" 84 | def __init__(self, avatarID): 85 | self.name = "Network Client Disconnection Event" 86 | self.avatarID = avatarID 87 | 88 | class GameSyncEvent(Event): 89 | """...""" 90 | def __init__(self, game): 91 | self.name = "Game Synched to Authoritative State" 92 | self.game = game 93 | 94 | class PlayerJoinRequest(Event): 95 | """...""" 96 | def __init__(self, playerDict): 97 | self.name = "Player Joining Game Request" 98 | self.playerDict = playerDict 99 | 100 | class PlayerJoinEvent(Event): 101 | """...""" 102 | def __init__(self, player): 103 | self.name = "Player Joined Game Event" 104 | self.player = player 105 | 106 | class CharactorPlaceRequest(Event): 107 | """...""" 108 | def __init__(self, player, charactor, sector): 109 | self.name = "Charactor Placement Request" 110 | self.player = player 111 | self.charactor = charactor 112 | self.sector = sector 113 | -------------------------------------------------------------------------------- /code_examples/network.py: -------------------------------------------------------------------------------- 1 | 2 | from example import * 3 | from twisted.spread import pb 4 | 5 | # A list of ALL possible events that a server can send to a client 6 | serverToClientEvents = [] 7 | # A list of ALL possible events that a client can send to a server 8 | clientToServerEvents = [] 9 | 10 | #------------------------------------------------------------------------------ 11 | #Mix-In Helper Functions 12 | #------------------------------------------------------------------------------ 13 | def MixInClass( origClass, addClass ): 14 | if addClass not in origClass.__bases__: 15 | origClass.__bases__ += (addClass,) 16 | 17 | #------------------------------------------------------------------------------ 18 | def MixInCopyClasses( someClass ): 19 | MixInClass( someClass, pb.Copyable ) 20 | MixInClass( someClass, pb.RemoteCopy ) 21 | 22 | #------------------------------------------------------------------------------ 23 | def serialize(obj, registry): 24 | objType = type(obj) 25 | if objType in [str, unicode, int, float, bool, type(None)]: 26 | return obj 27 | 28 | elif objType in [list, tuple]: 29 | new_obj = [] 30 | for sub_obj in obj: 31 | new_obj.append(serialize(sub_obj, registry)) 32 | return new_obj 33 | 34 | elif objType == dict: 35 | new_obj = {} 36 | for key, val in obj.items(): 37 | new_obj[serialize(key, registry)] = serialize(val, registry) 38 | return new_obj 39 | 40 | else: 41 | objID = id(obj) 42 | registry[objID] = obj 43 | return objID 44 | 45 | #------------------------------------------------------------------------------ 46 | class Serializable: 47 | '''The Serializable interface. 48 | All objects inheriting Serializable must have a .copyworthy_attrs member. 49 | ''' 50 | def getStateToCopy(self, registry): 51 | d = {} 52 | for attr in self.copyworthy_attrs: 53 | val = getattr(self, attr) 54 | new_val = serialize(val, registry) 55 | d[attr] = new_val 56 | 57 | return d 58 | 59 | 60 | #------------------------------------------------------------------------------ 61 | #------------------------------------------------------------------------------ 62 | # For each event class, if it is sendable over the network, we have 63 | # to Mix In the "copy classes", or make a replacement event class that is 64 | # copyable 65 | 66 | #------------------------------------------------------------------------------ 67 | # TickEvent 68 | # Direction: don't send. 69 | #The Tick event happens hundreds of times per second. If we think we need 70 | #to send it over the network, we should REALLY re-evaluate our design 71 | 72 | #------------------------------------------------------------------------------ 73 | # QuitEvent 74 | # Direction: Client to Server only 75 | MixInCopyClasses( QuitEvent ) 76 | pb.setUnjellyableForClass(QuitEvent, QuitEvent) 77 | clientToServerEvents.append( QuitEvent ) 78 | 79 | #------------------------------------------------------------------------------ 80 | # GameStartRequest 81 | # Direction: Client to Server only 82 | MixInCopyClasses( GameStartRequest ) 83 | pb.setUnjellyableForClass(GameStartRequest, GameStartRequest) 84 | clientToServerEvents.append( GameStartRequest ) 85 | 86 | 87 | 88 | #------------------------------------------------------------------------------ 89 | # ServerConnectEvent 90 | # Direction: don't send. 91 | # we don't need to send this over the network. 92 | 93 | #------------------------------------------------------------------------------ 94 | # ClientConnectEvent 95 | # Direction: don't send. 96 | # we don't need to send this over the network. 97 | 98 | #------------------------------------------------------------------------------ 99 | class ServerErrorEvent(object): 100 | def __init__(self): 101 | self.name = "Server Err Event" 102 | 103 | #------------------------------------------------------------------------------ 104 | class ClientErrorEvent(object): 105 | def __init__(self): 106 | self.name = "Client Err Event" 107 | 108 | #------------------------------------------------------------------------------ 109 | # GameStartedEvent 110 | # Direction: Server to Client only 111 | class CopyableGameStartedEvent(pb.Copyable, pb.RemoteCopy): 112 | def __init__(self, event, registry): 113 | self.name = "Copyable Game Started Event" 114 | self.gameID = id(event.game) 115 | registry[self.gameID] = event.game 116 | #TODO: put this in a Player Join Event or something 117 | for p in event.game.players: 118 | registry[id(p)] = p 119 | 120 | pb.setUnjellyableForClass(CopyableGameStartedEvent, CopyableGameStartedEvent) 121 | serverToClientEvents.append( CopyableGameStartedEvent ) 122 | 123 | #------------------------------------------------------------------------------ 124 | # MapBuiltEvent 125 | # Direction: Server to Client only 126 | class CopyableMapBuiltEvent( pb.Copyable, pb.RemoteCopy): 127 | def __init__(self, event, registry ): 128 | self.name = "Copyable Map Finished Building Event" 129 | self.mapID = id( event.map ) 130 | registry[self.mapID] = event.map 131 | 132 | pb.setUnjellyableForClass(CopyableMapBuiltEvent, CopyableMapBuiltEvent) 133 | serverToClientEvents.append( CopyableMapBuiltEvent ) 134 | 135 | #------------------------------------------------------------------------------ 136 | # CharactorMoveEvent 137 | # Direction: Server to Client only 138 | class CopyableCharactorMoveEvent( pb.Copyable, pb.RemoteCopy): 139 | def __init__(self, event, registry ): 140 | self.name = "Copyable " + event.name 141 | self.charactorID = id( event.charactor ) 142 | registry[self.charactorID] = event.charactor 143 | 144 | pb.setUnjellyableForClass(CopyableCharactorMoveEvent, CopyableCharactorMoveEvent) 145 | serverToClientEvents.append( CopyableCharactorMoveEvent ) 146 | 147 | #------------------------------------------------------------------------------ 148 | # CharactorPlaceEvent 149 | # Direction: Server to Client only 150 | class CopyableCharactorPlaceEvent( pb.Copyable, pb.RemoteCopy): 151 | def __init__(self, event, registry ): 152 | self.name = "Copyable " + event.name 153 | self.charactorID = id( event.charactor ) 154 | registry[self.charactorID] = event.charactor 155 | 156 | pb.setUnjellyableForClass(CopyableCharactorPlaceEvent, CopyableCharactorPlaceEvent) 157 | serverToClientEvents.append( CopyableCharactorPlaceEvent ) 158 | 159 | 160 | #------------------------------------------------------------------------------ 161 | class CopyableCharactor(Serializable): 162 | copyworthy_attrs = ['sector', 'state'] 163 | 164 | def setCopyableState(self, stateDict, registry): 165 | neededObjIDs = [] 166 | success = True 167 | 168 | self.state = stateDict['state'] 169 | 170 | if stateDict['sector'] == None: 171 | self.sector = None 172 | elif not registry.has_key( stateDict['sector'] ): 173 | registry[stateDict['sector']] = Sector(self.evManager) 174 | neededObjIDs.append( stateDict['sector'] ) 175 | success = False 176 | else: 177 | self.sector = registry[stateDict['sector']] 178 | 179 | return [success, neededObjIDs] 180 | 181 | 182 | MixInClass( Charactor, CopyableCharactor ) 183 | 184 | #------------------------------------------------------------------------------ 185 | # PlayerJoinRequest 186 | # Direction: Client to Server only 187 | MixInCopyClasses( PlayerJoinRequest ) 188 | pb.setUnjellyableForClass(PlayerJoinRequest, PlayerJoinRequest) 189 | clientToServerEvents.append( PlayerJoinRequest ) 190 | 191 | #------------------------------------------------------------------------------ 192 | # PlayerJoinEvent 193 | # Direction: Server to Client only 194 | class CopyablePlayerJoinEvent( pb.Copyable, pb.RemoteCopy): 195 | def __init__(self, event, registry): 196 | self.name = "Copyable " + event.name 197 | self.playerID = id(event.player) 198 | registry[self.playerID] = event.player 199 | pb.setUnjellyableForClass(CopyablePlayerJoinEvent, CopyablePlayerJoinEvent) 200 | serverToClientEvents.append( CopyablePlayerJoinEvent ) 201 | 202 | #------------------------------------------------------------------------------ 203 | # CharactorPlaceRequest 204 | # Direction: Client to Server only 205 | class CopyableCharactorPlaceRequest( pb.Copyable, pb.RemoteCopy): 206 | def __init__(self, event, registry ): 207 | self.name = "Copyable " + event.name 208 | self.playerID = None 209 | self.charactorID = None 210 | self.sectorID = None 211 | for key,val in registry.iteritems(): 212 | if val is event.player: 213 | print 'making char place request' 214 | print 'self.playerid', key 215 | self.playerID = key 216 | if val is event.charactor: 217 | self.charactorID = key 218 | if val is event.sector: 219 | self.sectorID = key 220 | if None in ( self.playerID, self.charactorID, self.sectorID): 221 | print "SOMETHING REALLY WRONG" 222 | print self.playerID, event.player 223 | print self.charactorID, event.charactor 224 | print self.sectorID, event.sector 225 | pb.setUnjellyableForClass(CopyableCharactorPlaceRequest, CopyableCharactorPlaceRequest) 226 | clientToServerEvents.append( CopyableCharactorPlaceRequest ) 227 | 228 | #------------------------------------------------------------------------------ 229 | # CharactorMoveRequest 230 | # Direction: Client to Server only 231 | class CopyableCharactorMoveRequest( pb.Copyable, pb.RemoteCopy): 232 | def __init__(self, event, registry ): 233 | self.name = "Copyable " + event.name 234 | self.direction = event.direction 235 | self.playerID = None 236 | self.charactorID = None 237 | for key,val in registry.iteritems(): 238 | if val is event.player: 239 | self.playerID = key 240 | if val is event.charactor: 241 | self.charactorID = key 242 | if None in ( self.playerID, self.charactorID): 243 | print "SOMETHING REALLY WRONG" 244 | print self.playerID, event.player 245 | print self.charactorID, event.charactor 246 | pb.setUnjellyableForClass(CopyableCharactorMoveRequest, CopyableCharactorMoveRequest) 247 | clientToServerEvents.append( CopyableCharactorMoveRequest ) 248 | 249 | #------------------------------------------------------------------------------ 250 | #------------------------------------------------------------------------------ 251 | # For any objects that we need to send in our events, we have to give them 252 | # getStateToCopy() and setCopyableState() methods so that we can send a 253 | # network-friendly representation of them over the network. 254 | 255 | #------------------------------------------------------------------------------ 256 | class CopyableMap: 257 | def getStateToCopy(self, registry): 258 | sectorIDList = [] 259 | for sect in self.sectors: 260 | sID = id(sect) 261 | sectorIDList.append( sID ) 262 | registry[sID] = sect 263 | 264 | return {'ninegrid':1, 'sectorIDList':sectorIDList} 265 | 266 | 267 | def setCopyableState(self, stateDict, registry): 268 | neededObjIDs = [] 269 | success = True 270 | 271 | if self.state != Map.STATE_BUILT: 272 | self.Build() 273 | 274 | for i, sectID in enumerate(stateDict['sectorIDList']): 275 | registry[sectID] = self.sectors[i] 276 | 277 | return [success, neededObjIDs] 278 | 279 | MixInClass( Map, CopyableMap ) 280 | 281 | 282 | #------------------------------------------------------------------------------ 283 | class CopyableGame(Serializable): 284 | copyworthy_attrs = ['map', 'state', 'players'] 285 | 286 | def setCopyableState(self, stateDict, registry): 287 | neededObjIDs = [] 288 | success = True 289 | 290 | self.state = stateDict['state'] 291 | 292 | if stateDict['map'] not in registry: 293 | registry[stateDict['map']] = Map( self.evManager ) 294 | neededObjIDs.append( stateDict['map'] ) 295 | success = False 296 | else: 297 | self.map = registry[stateDict['map']] 298 | 299 | self.players = [] 300 | for pID in stateDict['players']: 301 | if pID not in registry: 302 | registry[pID] = Player( self.evManager ) 303 | neededObjIDs.append( pID ) 304 | success = False 305 | else: 306 | self.players.append( registry[pID] ) 307 | 308 | return [success, neededObjIDs] 309 | 310 | MixInClass( Game, CopyableGame ) 311 | 312 | #------------------------------------------------------------------------------ 313 | class CopyablePlayer(Serializable): 314 | copyworthy_attrs = ['name', 'game', 'charactors'] 315 | 316 | def setCopyableState(self, stateDict, registry): 317 | neededObjIDs = [] 318 | success = True 319 | 320 | self.name = stateDict['name'] 321 | 322 | if not registry.has_key( stateDict['game'] ): 323 | print "Something is wrong. should already be a game" 324 | else: 325 | self.game = registry[stateDict['game']] 326 | 327 | self.charactors = [] 328 | for cID in stateDict['charactors']: 329 | if not cID in registry: 330 | registry[cID] = Charactor( self.evManager ) 331 | neededObjIDs.append( cID ) 332 | success = False 333 | else: 334 | self.charactors.append( registry[cID] ) 335 | 336 | return [success, neededObjIDs] 337 | 338 | MixInClass( Player, CopyablePlayer ) 339 | 340 | #------------------------------------------------------------------------------ 341 | # Copyable Sector is not necessary in this simple example because the sectors 342 | # all get copied over in CopyableMap 343 | #------------------------------------------------------------------------------ 344 | #------------------------------------------------------------------------------ 345 | class CopyableSector: 346 | def getStateToCopy(self, registry): 347 | return {} 348 | #d = self.__dict__.copy() 349 | #del d['evManager'] 350 | #d['neighbors'][DIRECTION_UP] = id(d['neighbors'][DIRECTION_UP]) 351 | #d['neighbors'][DIRECTION_DOWN] = id(d['neighbors'][DIRECTION_DOWN]) 352 | #d['neighbors'][DIRECTION_LEFT] = id(d['neighbors'][DIRECTION_LEFT]) 353 | #d['neighbors'][DIRECTION_RIGHT] = id(d['neighbors'][DIRECTION_RIGHT]) 354 | #return d 355 | 356 | def setCopyableState(self, stateDict, registry): 357 | return [True, []] 358 | #neededObjIDs = [] 359 | #success = True 360 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_UP]): 361 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_UP] ) 362 | #success = 0 363 | #else: 364 | #self.neighbors[DIRECTION_UP] = registry[stateDict['neighbors'][DIRECTION_UP]] 365 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_DOWN]): 366 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_DOWN] ) 367 | #success = 0 368 | #else: 369 | #self.neighbors[DIRECTION_DOWN] = registry[stateDict['neighbors'][DIRECTION_DOWN]] 370 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_LEFT]): 371 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_LEFT] ) 372 | #success = 0 373 | #else: 374 | #self.neighbors[DIRECTION_LEFT] = registry[stateDict['neighbors'][DIRECTION_LEFT]] 375 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_RIGHT]): 376 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_RIGHT] ) 377 | #success = 0 378 | #else: 379 | #self.neighbors[DIRECTION_RIGHT] = registry[stateDict['neighbors'][DIRECTION_RIGHT]] 380 | 381 | #return [success, neededObjIDs] 382 | 383 | MixInClass( Sector, CopyableSector ) 384 | -------------------------------------------------------------------------------- /conch_snippet.txt: -------------------------------------------------------------------------------- 1 | >>> from twisted.spread import pb 2 | >>> from twisted.internet import reactor 3 | >>> 4 | >>> factory = pb.PBClientFactory() 5 | >>> server = None 6 | >>> 7 | >>> def gotServer(serv): 8 | ... global server 9 | ... server = serv 10 | ... 11 | >>> connection = reactor.connectTCP('localhost', 8000, factory) 12 | >>> d = factory.getRootObject() 13 | >>> d.addCallback(gotServer) 14 | <Deferred at 0xc227a0 current result: None> 15 | >>> server.callRemote('GameStartRequest') 16 | <Deferred #0> 17 | Deferred #0 called back: 1 18 | >>> up, right, down, left = 0,1,2,3 19 | >>> server.callRemote('CharactorMoveRequest', up) 20 | <Deferred #1> 21 | Deferred #1 called back: 1 22 | >>> server.callRemote('CharactorMoveRequest', right) 23 | <Deferred #2> 24 | Deferred #2 called back: 1 25 | >>> server.callRemote('CharactorMoveRequest', down) 26 | <Deferred #3> 27 | Deferred #3 called back: 1 28 | >>> server.callRemote('CharactorMoveRequest', left) 29 | <Deferred #4> 30 | Deferred #4 called back: 1 31 | -------------------------------------------------------------------------------- /diagram-channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-channel.png -------------------------------------------------------------------------------- /diagram-gui-cutscene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-gui-cutscene.png -------------------------------------------------------------------------------- /diagram-gui-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-gui-dialog.png -------------------------------------------------------------------------------- /diagram-gui-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-gui-main.png -------------------------------------------------------------------------------- /diagram-gui-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-gui-menu.png -------------------------------------------------------------------------------- /diagram-gui-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-gui-options.png -------------------------------------------------------------------------------- /diagram-incoming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-incoming.png -------------------------------------------------------------------------------- /diagram-outgoing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-outgoing.png -------------------------------------------------------------------------------- /diagram-serialization-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-serialization-flowchart.png -------------------------------------------------------------------------------- /diagram-visualstages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/diagram-visualstages.png -------------------------------------------------------------------------------- /event_managers/events.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | #------------------------------------------------------------------------------ 4 | class EventManager: 5 | """this object is responsible for coordinating most communication 6 | between the Model, View, and Controller.""" 7 | def __init__(self): 8 | from weakref import WeakKeyDictionary 9 | self.listeners = WeakKeyDictionary() 10 | self.eventQueue= [] 11 | self.listenersToAdd = [] 12 | self.listenersToRemove = [] 13 | 14 | #---------------------------------------------------------------------- 15 | def RegisterListener( self, listener ): 16 | self.listenersToAdd.append(listener) 17 | 18 | #---------------------------------------------------------------------- 19 | def ActuallyUpdateListeners(self): 20 | for listener in self.listenersToAdd: 21 | self.listeners[ listener ] = 1 22 | for listener in self.listenersToRemove: 23 | if listener in self.listeners: 24 | del self.listeners[ listener ] 25 | 26 | #---------------------------------------------------------------------- 27 | def UnregisterListener( self, listener ): 28 | self.listenersToRemove.append(listener) 29 | 30 | #---------------------------------------------------------------------- 31 | def Post( self, event ): 32 | self.eventQueue.append(event) 33 | if isinstance(event, TickEvent): 34 | # Consume the event queue every Tick. 35 | self.ActuallyUpdateListeners() 36 | self.ConsumeEventQueue() 37 | 38 | #---------------------------------------------------------------------- 39 | def ConsumeEventQueue(self): 40 | i = 0 41 | while i < len( self.eventQueue ): 42 | event = self.eventQueue[i] 43 | for listener in self.listeners: 44 | # Note: a side effect of notifying the listener 45 | # could be that more events are put on the queue 46 | # or listeners could Register / Unregister 47 | listener.Notify( event ) 48 | i += 1 49 | if self.listenersToAdd: 50 | self.ActuallyUpdateListeners() 51 | #all code paths that could possibly add more events to 52 | # the eventQueue have been exhausted at this point, so 53 | # it's safe to empty the queue 54 | self.eventQueue= [] 55 | 56 | import Queue 57 | class QueueEventManager(EventManager): 58 | def __init__(self): 59 | from weakref import WeakKeyDictionary 60 | self.listeners = WeakKeyDictionary() 61 | self.eventQueue = Queue.Queue() 62 | self.listenersToAdd = [] 63 | self.listenersToRemove = [] 64 | 65 | #---------------------------------------------------------------------- 66 | def Post( self, event ): 67 | self.eventQueue.put(event) 68 | if isinstance(event, TickEvent): 69 | # Consume the event queue every Tick. 70 | self.ActuallyUpdateListeners() 71 | self.ConsumeEventQueue() 72 | 73 | #---------------------------------------------------------------------- 74 | def ConsumeEventQueue(self): 75 | try: 76 | while True: 77 | event = self.eventQueue.get(block=False) 78 | for listener in self.listeners: 79 | # Note: a side effect of notifying the listener 80 | # could be that more events are put on the queue 81 | # or listeners could Register / Unregister 82 | listener.Notify( event ) 83 | if self.listenersToAdd: 84 | self.ActuallyUpdateListeners() 85 | except Queue.Empty: 86 | pass # print 'queue empty', self.eventQueue 87 | 88 | 89 | class Event: 90 | """this is a superclass for any events that might be generated by an 91 | object and sent to the EventManager""" 92 | def __init__(self): 93 | self.name = "Generic Event" 94 | def __str__(self): 95 | return '<%10s %s>' % (self.__class__.__name__, id(self)) 96 | 97 | class TickEvent(Event): 98 | def __init__(self): 99 | self.name = "Tick" 100 | 101 | class EventA(Event): 102 | def __init__(self): 103 | self.name = "Event A" 104 | 105 | class EventB(Event): 106 | def __init__(self): 107 | self.name = "Event B" 108 | 109 | class EventC(Event): 110 | def __init__(self): 111 | self.name = "Event C" 112 | 113 | class Listener: 114 | def __init__(self, evManager): 115 | self.evManager = evManager 116 | self.evManager.RegisterListener(self) 117 | 118 | def __str__(self): 119 | return '<%20s %s>' % (self.__class__.__name__, id(self)) 120 | 121 | def Notify(self, event): 122 | print self, 'got event', event 123 | 124 | class ListenerAndPoster(Listener): 125 | def __init__(self, evManager): 126 | self.evManager = evManager 127 | self.evManager.RegisterListener(self) 128 | 129 | def Notify(self, event): 130 | print self, 'got event', event 131 | if isinstance(event, EventA): 132 | newEvent = EventC() 133 | self.evManager.Post(newEvent) 134 | 135 | def main(): 136 | evManager = EventManager() 137 | l1 = Listener(evManager) 138 | l2 = ListenerAndPoster(evManager) 139 | 140 | evManager.Post(EventA()) 141 | evManager.Post(EventB()) 142 | evManager.Post(TickEvent()) 143 | 144 | evManager = QueueEventManager() 145 | l1 = Listener(evManager) 146 | l2 = ListenerAndPoster(evManager) 147 | 148 | evManager.Post(EventA()) 149 | evManager.Post(EventB()) 150 | evManager.Post(TickEvent()) 151 | 152 | if __name__ == '__main__': 153 | main() 154 | -------------------------------------------------------------------------------- /examples/example1.py: -------------------------------------------------------------------------------- 1 | def Debug( msg ): 2 | print msg 3 | 4 | DIRECTION_UP = 0 5 | DIRECTION_DOWN = 1 6 | DIRECTION_LEFT = 2 7 | DIRECTION_RIGHT = 3 8 | 9 | class Event: 10 | """this is a superclass for any events that might be generated by an 11 | object and sent to the EventManager""" 12 | def __init__(self): 13 | self.name = "Generic Event" 14 | 15 | class TickEvent(Event): 16 | def __init__(self): 17 | self.name = "CPU Tick Event" 18 | 19 | class QuitEvent(Event): 20 | def __init__(self): 21 | self.name = "Program Quit Event" 22 | 23 | class MapBuiltEvent(Event): 24 | def __init__(self, gameMap): 25 | self.name = "Map Finished Building Event" 26 | self.map = gameMap 27 | 28 | class GameStartedEvent(Event): 29 | def __init__(self, game): 30 | self.name = "Game Started Event" 31 | self.game = game 32 | 33 | class CharactorMoveRequest(Event): 34 | def __init__(self, direction): 35 | self.name = "Charactor Move Request" 36 | self.direction = direction 37 | 38 | class CharactorPlaceEvent(Event): 39 | """this event occurs when a Charactor is *placed* in a sector, 40 | ie it doesn't move there from an adjacent sector.""" 41 | def __init__(self, charactor): 42 | self.name = "Charactor Placement Event" 43 | self.charactor = charactor 44 | 45 | class CharactorMoveEvent(Event): 46 | def __init__(self, charactor): 47 | self.name = "Charactor Move Event" 48 | self.charactor = charactor 49 | 50 | #------------------------------------------------------------------------------ 51 | class EventManager: 52 | """this object is responsible for coordinating most communication 53 | between the Model, View, and Controller.""" 54 | def __init__(self ): 55 | from weakref import WeakKeyDictionary 56 | self.listeners = WeakKeyDictionary() 57 | self.eventQueue= [] 58 | 59 | #---------------------------------------------------------------------- 60 | def RegisterListener( self, listener ): 61 | self.listeners[ listener ] = 1 62 | 63 | #---------------------------------------------------------------------- 64 | def UnregisterListener( self, listener ): 65 | if listener in self.listeners: 66 | del self.listeners[ listener ] 67 | 68 | #---------------------------------------------------------------------- 69 | def Post( self, event ): 70 | if not isinstance(event, TickEvent): 71 | Debug( " Message: " + event.name ) 72 | for listener in self.listeners: 73 | #NOTE: If the weakref has died, it will be 74 | #automatically removed, so we don't have 75 | #to worry about it. 76 | listener.Notify( event ) 77 | 78 | #------------------------------------------------------------------------------ 79 | class KeyboardController: 80 | """...""" 81 | def __init__(self, evManager): 82 | self.evManager = evManager 83 | self.evManager.RegisterListener( self ) 84 | 85 | #---------------------------------------------------------------------- 86 | def Notify(self, event): 87 | if isinstance( event, TickEvent ): 88 | #Handle Input Events 89 | for event in pygame.event.get(): 90 | ev = None 91 | if event.type == QUIT: 92 | ev = QuitEvent() 93 | elif event.type == KEYDOWN \ 94 | and event.key == K_ESCAPE: 95 | ev = QuitEvent() 96 | elif event.type == KEYDOWN \ 97 | and event.key == K_UP: 98 | direction = DIRECTION_UP 99 | ev = CharactorMoveRequest(direction) 100 | elif event.type == KEYDOWN \ 101 | and event.key == K_DOWN: 102 | direction = DIRECTION_DOWN 103 | ev = CharactorMoveRequest(direction) 104 | elif event.type == KEYDOWN \ 105 | and event.key == K_LEFT: 106 | direction = DIRECTION_LEFT 107 | ev = CharactorMoveRequest(direction) 108 | elif event.type == KEYDOWN \ 109 | and event.key == K_RIGHT: 110 | direction = DIRECTION_RIGHT 111 | ev = CharactorMoveRequest(direction) 112 | 113 | if ev: 114 | self.evManager.Post( ev ) 115 | 116 | 117 | #------------------------------------------------------------------------------ 118 | class CPUSpinnerController: 119 | """...""" 120 | def __init__(self, evManager): 121 | self.evManager = evManager 122 | self.evManager.RegisterListener( self ) 123 | 124 | self.keepGoing = 1 125 | 126 | #---------------------------------------------------------------------- 127 | def Run(self): 128 | while self.keepGoing: 129 | event = TickEvent() 130 | self.evManager.Post( event ) 131 | 132 | #---------------------------------------------------------------------- 133 | def Notify(self, event): 134 | if isinstance( event, QuitEvent ): 135 | #this will stop the while loop from running 136 | self.keepGoing = False 137 | 138 | 139 | import pygame 140 | from pygame.locals import * 141 | #------------------------------------------------------------------------------ 142 | class SectorSprite(pygame.sprite.Sprite): 143 | def __init__(self, sector, group=None): 144 | pygame.sprite.Sprite.__init__(self, group) 145 | self.image = pygame.Surface( (128,128) ) 146 | self.image.fill( (0,255,128) ) 147 | 148 | self.sector = sector 149 | 150 | #------------------------------------------------------------------------------ 151 | class CharactorSprite(pygame.sprite.Sprite): 152 | def __init__(self, group=None): 153 | pygame.sprite.Sprite.__init__(self, group) 154 | 155 | charactorSurf = pygame.Surface( (64,64) ) 156 | charactorSurf = charactorSurf.convert_alpha() 157 | charactorSurf.fill((0,0,0,0)) #make transparent 158 | pygame.draw.circle( charactorSurf, (255,0,0), (32,32), 32 ) 159 | self.image = charactorSurf 160 | self.rect = charactorSurf.get_rect() 161 | 162 | self.moveTo = None 163 | 164 | #---------------------------------------------------------------------- 165 | def update(self): 166 | if self.moveTo: 167 | self.rect.center = self.moveTo 168 | self.moveTo = None 169 | 170 | #------------------------------------------------------------------------------ 171 | class PygameView: 172 | """...""" 173 | def __init__(self, evManager): 174 | self.evManager = evManager 175 | self.evManager.RegisterListener( self ) 176 | 177 | pygame.init() 178 | self.window = pygame.display.set_mode( (424,440) ) 179 | pygame.display.set_caption( 'Example Game' ) 180 | self.background = pygame.Surface( self.window.get_size() ) 181 | self.background.fill( (0,0,0) ) 182 | 183 | self.backSprites = pygame.sprite.RenderUpdates() 184 | self.frontSprites = pygame.sprite.RenderUpdates() 185 | 186 | 187 | #---------------------------------------------------------------------- 188 | def ShowMap(self, gameMap): 189 | squareRect = pygame.Rect( (-128,10, 128,128 ) ) 190 | 191 | i = 0 192 | for sector in gameMap.sectors: 193 | if i < 3: 194 | squareRect = squareRect.move( 138,0 ) 195 | else: 196 | i = 0 197 | squareRect = squareRect.move( -(138*2), 138 ) 198 | i += 1 199 | newSprite = SectorSprite( sector, self.backSprites ) 200 | newSprite.rect = squareRect 201 | newSprite = None 202 | 203 | #---------------------------------------------------------------------- 204 | def ShowCharactor(self, charactor): 205 | charactorSprite = CharactorSprite( self.frontSprites ) 206 | 207 | sector = charactor.sector 208 | sectorSprite = self.GetSectorSprite( sector ) 209 | charactorSprite.rect.center = sectorSprite.rect.center 210 | 211 | #---------------------------------------------------------------------- 212 | def MoveCharactor(self, charactor): 213 | charactorSprite = self.GetCharactorSprite( charactor ) 214 | 215 | sector = charactor.sector 216 | sectorSprite = self.GetSectorSprite( sector ) 217 | 218 | charactorSprite.moveTo = sectorSprite.rect.center 219 | 220 | #---------------------------------------------------------------------- 221 | def GetCharactorSprite(self, charactor): 222 | #there will be only one 223 | for s in self.frontSprites: 224 | return s 225 | return None 226 | 227 | #---------------------------------------------------------------------- 228 | def GetSectorSprite(self, sector): 229 | for s in self.backSprites: 230 | if hasattr(s, "sector") and s.sector == sector: 231 | return s 232 | 233 | 234 | #---------------------------------------------------------------------- 235 | def Notify(self, event): 236 | if isinstance( event, TickEvent ): 237 | #Draw Everything 238 | self.backSprites.clear( self.window, self.background ) 239 | self.frontSprites.clear( self.window, self.background ) 240 | 241 | self.backSprites.update() 242 | self.frontSprites.update() 243 | 244 | dirtyRects1 = self.backSprites.draw( self.window ) 245 | dirtyRects2 = self.frontSprites.draw( self.window ) 246 | 247 | dirtyRects = dirtyRects1 + dirtyRects2 248 | pygame.display.update( dirtyRects ) 249 | 250 | 251 | elif isinstance( event, MapBuiltEvent ): 252 | gameMap = event.map 253 | self.ShowMap( gameMap ) 254 | 255 | elif isinstance( event, CharactorPlaceEvent ): 256 | self.ShowCharactor( event.charactor ) 257 | 258 | elif isinstance( event, CharactorMoveEvent ): 259 | self.MoveCharactor( event.charactor ) 260 | 261 | 262 | #------------------------------------------------------------------------------ 263 | class Game: 264 | """...""" 265 | 266 | STATE_PREPARING = 0 267 | STATE_RUNNING = 1 268 | STATE_PAUSED = 2 269 | 270 | #---------------------------------------------------------------------- 271 | def __init__(self, evManager): 272 | self.evManager = evManager 273 | self.evManager.RegisterListener( self ) 274 | 275 | self.state = Game.STATE_PREPARING 276 | 277 | self.players = [ Player(evManager) ] 278 | self.map = Map( evManager ) 279 | 280 | #---------------------------------------------------------------------- 281 | def Start(self): 282 | self.map.Build() 283 | self.state = Game.STATE_RUNNING 284 | ev = GameStartedEvent( self ) 285 | self.evManager.Post( ev ) 286 | 287 | #---------------------------------------------------------------------- 288 | def Notify(self, event): 289 | if isinstance( event, TickEvent ): 290 | if self.state == Game.STATE_PREPARING: 291 | self.Start() 292 | 293 | #------------------------------------------------------------------------------ 294 | class Player: 295 | """...""" 296 | def __init__(self, evManager): 297 | self.evManager = evManager 298 | #self.evManager.RegisterListener( self ) 299 | 300 | self.charactors = [ Charactor(evManager) ] 301 | 302 | #------------------------------------------------------------------------------ 303 | class Charactor: 304 | """...""" 305 | def __init__(self, evManager): 306 | self.evManager = evManager 307 | self.evManager.RegisterListener( self ) 308 | self.sector = None 309 | 310 | #---------------------------------------------------------------------- 311 | def Move(self, direction): 312 | if self.sector.MovePossible( direction ): 313 | newSector = self.sector.neighbors[direction] 314 | self.sector = newSector 315 | ev = CharactorMoveEvent( self ) 316 | self.evManager.Post( ev ) 317 | 318 | #---------------------------------------------------------------------- 319 | def Place(self, sector): 320 | self.sector = sector 321 | ev = CharactorPlaceEvent( self ) 322 | self.evManager.Post( ev ) 323 | 324 | #---------------------------------------------------------------------- 325 | def Notify(self, event): 326 | if isinstance( event, GameStartedEvent ): 327 | gameMap = event.game.map 328 | self.Place( gameMap.sectors[gameMap.startSectorIndex] ) 329 | 330 | elif isinstance( event, CharactorMoveRequest ): 331 | self.Move( event.direction ) 332 | 333 | #------------------------------------------------------------------------------ 334 | class Map: 335 | """...""" 336 | 337 | STATE_PREPARING = 0 338 | STATE_BUILT = 1 339 | 340 | 341 | #---------------------------------------------------------------------- 342 | def __init__(self, evManager): 343 | self.evManager = evManager 344 | #self.evManager.RegisterListener( self ) 345 | 346 | self.state = Map.STATE_PREPARING 347 | 348 | self.sectors = range(9) 349 | self.startSectorIndex = 0 350 | 351 | #---------------------------------------------------------------------- 352 | def Build(self): 353 | for i in range(9): 354 | self.sectors[i] = Sector( self.evManager ) 355 | 356 | self.sectors[3].neighbors[DIRECTION_UP] = self.sectors[0] 357 | self.sectors[4].neighbors[DIRECTION_UP] = self.sectors[1] 358 | self.sectors[5].neighbors[DIRECTION_UP] = self.sectors[2] 359 | self.sectors[6].neighbors[DIRECTION_UP] = self.sectors[3] 360 | self.sectors[7].neighbors[DIRECTION_UP] = self.sectors[4] 361 | self.sectors[8].neighbors[DIRECTION_UP] = self.sectors[5] 362 | 363 | self.sectors[0].neighbors[DIRECTION_DOWN] = self.sectors[3] 364 | self.sectors[1].neighbors[DIRECTION_DOWN] = self.sectors[4] 365 | self.sectors[2].neighbors[DIRECTION_DOWN] = self.sectors[5] 366 | self.sectors[3].neighbors[DIRECTION_DOWN] = self.sectors[6] 367 | self.sectors[4].neighbors[DIRECTION_DOWN] = self.sectors[7] 368 | self.sectors[5].neighbors[DIRECTION_DOWN] = self.sectors[8] 369 | 370 | self.sectors[1].neighbors[DIRECTION_LEFT] = self.sectors[0] 371 | self.sectors[2].neighbors[DIRECTION_LEFT] = self.sectors[1] 372 | self.sectors[4].neighbors[DIRECTION_LEFT] = self.sectors[3] 373 | self.sectors[5].neighbors[DIRECTION_LEFT] = self.sectors[4] 374 | self.sectors[7].neighbors[DIRECTION_LEFT] = self.sectors[6] 375 | self.sectors[8].neighbors[DIRECTION_LEFT] = self.sectors[7] 376 | 377 | self.sectors[0].neighbors[DIRECTION_RIGHT] = self.sectors[1] 378 | self.sectors[1].neighbors[DIRECTION_RIGHT] = self.sectors[2] 379 | self.sectors[3].neighbors[DIRECTION_RIGHT] = self.sectors[4] 380 | self.sectors[4].neighbors[DIRECTION_RIGHT] = self.sectors[5] 381 | self.sectors[6].neighbors[DIRECTION_RIGHT] = self.sectors[7] 382 | self.sectors[7].neighbors[DIRECTION_RIGHT] = self.sectors[8] 383 | 384 | self.state = Map.STATE_BUILT 385 | 386 | ev = MapBuiltEvent( self ) 387 | self.evManager.Post( ev ) 388 | 389 | #------------------------------------------------------------------------------ 390 | class Sector: 391 | """...""" 392 | def __init__(self, evManager): 393 | self.evManager = evManager 394 | #self.evManager.RegisterListener( self ) 395 | 396 | self.neighbors = range(4) 397 | 398 | self.neighbors[DIRECTION_UP] = None 399 | self.neighbors[DIRECTION_DOWN] = None 400 | self.neighbors[DIRECTION_LEFT] = None 401 | self.neighbors[DIRECTION_RIGHT] = None 402 | 403 | #---------------------------------------------------------------------- 404 | def MovePossible(self, direction): 405 | if self.neighbors[direction]: 406 | return 1 407 | 408 | 409 | #------------------------------------------------------------------------------ 410 | def main(): 411 | """...""" 412 | evManager = EventManager() 413 | 414 | keybd = KeyboardController( evManager ) 415 | spinner = CPUSpinnerController( evManager ) 416 | pygameView = PygameView( evManager ) 417 | game = Game( evManager ) 418 | 419 | spinner.Run() 420 | 421 | if __name__ == "__main__": 422 | main() 423 | -------------------------------------------------------------------------------- /examples/example2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/examples/example2.tar.gz -------------------------------------------------------------------------------- /examples/example2/client.py: -------------------------------------------------------------------------------- 1 | import network 2 | from twisted.spread import pb 3 | from twisted.internet.selectreactor import SelectReactor 4 | from twisted.internet.main import installReactor 5 | from events import * 6 | from example1 import (EventManager, 7 | Game, 8 | KeyboardController, 9 | CPUSpinnerController, 10 | PygameView) 11 | 12 | serverHost, serverPort = 'localhost', 8000 13 | 14 | #------------------------------------------------------------------------------ 15 | class NetworkServerView(pb.Root): 16 | """We SEND events to the server through this object""" 17 | STATE_PREPARING = 0 18 | STATE_CONNECTING = 1 19 | STATE_CONNECTED = 2 20 | STATE_DISCONNECTING = 3 21 | STATE_DISCONNECTED = 4 22 | 23 | #---------------------------------------------------------------------- 24 | def __init__(self, evManager, sharedObjectRegistry): 25 | self.evManager = evManager 26 | self.evManager.RegisterListener( self ) 27 | 28 | self.pbClientFactory = pb.PBClientFactory() 29 | self.state = NetworkServerView.STATE_PREPARING 30 | self.reactor = None 31 | self.server = None 32 | 33 | self.sharedObjs = sharedObjectRegistry 34 | 35 | #---------------------------------------------------------------------- 36 | def AttemptConnection(self): 37 | print "attempting a connection to", serverHost, serverPort 38 | self.state = NetworkServerView.STATE_CONNECTING 39 | if self.reactor: 40 | self.reactor.stop() 41 | self.PumpReactor() 42 | else: 43 | self.reactor = SelectReactor() 44 | installReactor(self.reactor) 45 | connection = self.reactor.connectTCP(serverHost, serverPort, 46 | self.pbClientFactory) 47 | deferred = self.pbClientFactory.getRootObject() 48 | deferred.addCallback(self.Connected) 49 | deferred.addErrback(self.ConnectFailed) 50 | self.reactor.startRunning() 51 | 52 | #---------------------------------------------------------------------- 53 | def Disconnect(self): 54 | print "disconnecting" 55 | if not self.reactor: 56 | return 57 | print 'stopping the reactor' 58 | self.reactor.stop() 59 | self.PumpReactor() 60 | self.state = NetworkServerView.STATE_DISCONNECTING 61 | 62 | #---------------------------------------------------------------------- 63 | def Connected(self, server): 64 | print "CONNECTED" 65 | self.server = server 66 | self.state = NetworkServerView.STATE_CONNECTED 67 | ev = ServerConnectEvent( server ) 68 | self.evManager.Post( ev ) 69 | 70 | #---------------------------------------------------------------------- 71 | def ConnectFailed(self, server): 72 | print "CONNECTION FAILED" 73 | #self.state = NetworkServerView.STATE_PREPARING 74 | self.state = NetworkServerView.STATE_DISCONNECTED 75 | 76 | #---------------------------------------------------------------------- 77 | def PumpReactor(self): 78 | self.reactor.runUntilCurrent() 79 | self.reactor.doIteration(0) 80 | 81 | #---------------------------------------------------------------------- 82 | def Notify(self, event): 83 | NSV = NetworkServerView 84 | if isinstance( event, TickEvent ): 85 | if self.state == NSV.STATE_PREPARING: 86 | self.AttemptConnection() 87 | elif self.state in [NSV.STATE_CONNECTED, 88 | NSV.STATE_DISCONNECTING, 89 | NSV.STATE_CONNECTING]: 90 | self.PumpReactor() 91 | return 92 | 93 | if isinstance( event, QuitEvent ): 94 | self.Disconnect() 95 | return 96 | 97 | ev = event 98 | if not isinstance( event, pb.Copyable ): 99 | evName = event.__class__.__name__ 100 | copyableClsName = "Copyable"+evName 101 | if not hasattr( network, copyableClsName ): 102 | return 103 | copyableClass = getattr( network, copyableClsName ) 104 | ev = copyableClass( event, self.sharedObjs ) 105 | 106 | if ev.__class__ not in network.clientToServerEvents: 107 | #print "CLIENT NOT SENDING: " +str(ev) 108 | return 109 | 110 | if self.server: 111 | print " ==== Client sending", str(ev) 112 | remoteCall = self.server.callRemote("EventOverNetwork", 113 | ev) 114 | else: 115 | print " =--= Cannot send while disconnected:", str(ev) 116 | 117 | 118 | 119 | 120 | #------------------------------------------------------------------------------ 121 | class NetworkServerController(pb.Referenceable): 122 | """We RECEIVE events from the server through this object""" 123 | def __init__(self, evManager): 124 | self.evManager = evManager 125 | self.evManager.RegisterListener( self ) 126 | 127 | #---------------------------------------------------------------------- 128 | def remote_ServerEvent(self, event): 129 | print " ==== GOT AN EVENT FROM SERVER:", str(event) 130 | self.evManager.Post( event ) 131 | return 1 132 | 133 | #---------------------------------------------------------------------- 134 | def Notify(self, event): 135 | if isinstance( event, ServerConnectEvent ): 136 | #tell the server that we're listening to it and 137 | #it can access this object 138 | event.server.callRemote("ClientConnect", self) 139 | 140 | 141 | #------------------------------------------------------------------------------ 142 | class PhonyEventManager(EventManager): 143 | #---------------------------------------------------------------------- 144 | def Post( self, event ): 145 | pass 146 | 147 | #------------------------------------------------------------------------------ 148 | class PhonyModel(object): 149 | '''This isn't the authouritative model. That one exists on the 150 | server. This is a model to store local state and to interact with 151 | the local EventManager. 152 | ''' 153 | def __init__(self, evManager, sharedObjectRegistry): 154 | self.sharedObjs = sharedObjectRegistry 155 | self.game = None 156 | self.server = None 157 | self.phonyEvManager = PhonyEventManager() 158 | self.realEvManager = evManager 159 | 160 | self.realEvManager.RegisterListener( self ) 161 | 162 | #---------------------------------------------------------------------- 163 | def StateReturned(self, response): 164 | if response[0] == 0: 165 | print "GOT ZERO -- better error handler here" 166 | return None 167 | objID = response[0] 168 | objDict = response[1] 169 | obj = self.sharedObjs[objID] 170 | 171 | retval = obj.setCopyableState(objDict, self.sharedObjs) 172 | if retval[0] == 1: 173 | return obj 174 | for remainingObjID in retval[1]: 175 | remoteResponse = self.server.callRemote("GetObjectState", remainingObjID) 176 | remoteResponse.addCallback(self.StateReturned) 177 | 178 | #TODO: look under the Twisted Docs for "Chaining Defferreds" 179 | retval = obj.setCopyableState(objDict, self.sharedObjs) 180 | if retval[0] == 0: 181 | print "WEIRD!!!!!!!!!!!!!!!!!!" 182 | return None 183 | 184 | return obj 185 | 186 | #---------------------------------------------------------------------- 187 | def Notify(self, event): 188 | if isinstance( event, ServerConnectEvent ): 189 | self.server = event.server 190 | elif isinstance( event, network.CopyableGameStartedEvent ): 191 | gameID = event.gameID 192 | if not self.game: 193 | # give a phony event manager to the local game 194 | # object so it won't be able to fire events 195 | self.game = Game( self.phonyEvManager ) 196 | self.sharedObjs[gameID] = self.game 197 | #------------------------ 198 | #note: we shouldn't really be calling methods on our 199 | # phony model, instead we should be copying the state 200 | # from the server. 201 | #self.game.Start() 202 | #------------------------ 203 | print 'sending the gse to the real em.' 204 | ev = GameStartedEvent( self.game ) 205 | self.realEvManager.Post( ev ) 206 | 207 | if isinstance( event, network.CopyableMapBuiltEvent ): 208 | mapID = event.mapID 209 | if not self.game: 210 | self.game = Game( self.phonyEvManager ) 211 | if self.sharedObjs.has_key(mapID): 212 | map = self.sharedObjs[mapID] 213 | ev = MapBuiltEvent( map ) 214 | self.realEvManager.Post( ev ) 215 | else: 216 | map = self.game.map 217 | self.sharedObjs[mapID] = map 218 | remoteResponse = self.server.callRemote("GetObjectState", mapID) 219 | remoteResponse.addCallback(self.StateReturned) 220 | remoteResponse.addCallback(self.MapBuiltCallback) 221 | 222 | if isinstance( event, network.CopyableCharactorPlaceEvent ): 223 | charactorID = event.charactorID 224 | if self.sharedObjs.has_key(charactorID): 225 | charactor = self.sharedObjs[charactorID] 226 | ev = CharactorPlaceEvent( charactor ) 227 | self.realEvManager.Post( ev ) 228 | else: 229 | charactor = self.game.players[0].charactors[0] 230 | self.sharedObjs[charactorID] = charactor 231 | remoteResponse = self.server.callRemote("GetObjectState", charactorID) 232 | remoteResponse.addCallback(self.StateReturned) 233 | remoteResponse.addCallback(self.CharactorPlaceCallback) 234 | 235 | if isinstance( event, network.CopyableCharactorMoveEvent ): 236 | charactorID = event.charactorID 237 | if self.sharedObjs.has_key(charactorID): 238 | charactor = self.sharedObjs[charactorID] 239 | else: 240 | charactor = self.game.players[0].charactors[0] 241 | self.sharedObjs[charactorID] = charactor 242 | remoteResponse = self.server.callRemote("GetObjectState", charactorID) 243 | remoteResponse.addCallback(self.StateReturned) 244 | remoteResponse.addCallback(self.CharactorMoveCallback) 245 | 246 | #---------------------------------------------------------------------- 247 | def CharactorPlaceCallback(self, charactor): 248 | ev = CharactorPlaceEvent( charactor ) 249 | self.realEvManager.Post( ev ) 250 | #---------------------------------------------------------------------- 251 | def MapBuiltCallback(self, map): 252 | ev = MapBuiltEvent( map ) 253 | self.realEvManager.Post( ev ) 254 | #---------------------------------------------------------------------- 255 | def CharactorMoveCallback(self, charactor): 256 | ev = CharactorMoveEvent( charactor ) 257 | self.realEvManager.Post( ev ) 258 | 259 | 260 | #------------------------------------------------------------------------------ 261 | def main(): 262 | evManager = EventManager() 263 | sharedObjectRegistry = {} 264 | 265 | keybd = KeyboardController( evManager ) 266 | spinner = CPUSpinnerController( evManager ) 267 | pygameView = PygameView( evManager ) 268 | 269 | phonyModel = PhonyModel( evManager, sharedObjectRegistry ) 270 | 271 | #from twisted.spread.jelly import globalSecurity 272 | #globalSecurity.allowModules( network ) 273 | 274 | serverController = NetworkServerController( evManager ) 275 | serverView = NetworkServerView( evManager, sharedObjectRegistry ) 276 | 277 | spinner.Run() 278 | print 'Done Run' 279 | print evManager.eventQueue 280 | 281 | if __name__ == "__main__": 282 | main() 283 | -------------------------------------------------------------------------------- /examples/example2/events.py: -------------------------------------------------------------------------------- 1 | #SECURITY NOTE: anything in here can be created simply by sending the 2 | # class name over the network. This is a potential vulnerability 3 | # I wouldn't suggest letting any of these classes DO anything, especially 4 | # things like file system access, or allocating huge amounts of memory 5 | 6 | class Event: 7 | """this is a superclass for any events that might be generated by an 8 | object and sent to the EventManager""" 9 | def __init__(self): 10 | self.name = "Generic Event" 11 | 12 | class TickEvent(Event): 13 | def __init__(self): 14 | self.name = "CPU Tick Event" 15 | 16 | class QuitEvent(Event): 17 | def __init__(self): 18 | self.name = "Program Quit Event" 19 | 20 | class MapBuiltEvent(Event): 21 | def __init__(self, map): 22 | self.name = "Map Finished Building Event" 23 | self.map = map 24 | 25 | class GameStartRequest(Event): 26 | def __init__(self): 27 | self.name = "Game Start Request" 28 | 29 | class GameStartedEvent(Event): 30 | def __init__(self, game): 31 | self.name = "Game Started Event" 32 | self.game = game 33 | 34 | class CharactorMoveRequest(Event): 35 | def __init__(self, direction): 36 | self.name = "Charactor Move Request" 37 | self.direction = direction 38 | 39 | class CharactorMoveEvent(Event): 40 | def __init__(self, charactor): 41 | self.name = "Charactor Move Event" 42 | self.charactor = charactor 43 | 44 | class CharactorPlaceEvent(Event): 45 | """this event occurs when a Charactor is *placed* in a sector, 46 | ie it doesn't move there from an adjacent sector.""" 47 | def __init__(self, charactor): 48 | self.name = "Charactor Placement Event" 49 | self.charactor = charactor 50 | 51 | class ServerConnectEvent(Event): 52 | """the client generates this when it detects that it has successfully 53 | connected to the server""" 54 | def __init__(self, serverReference): 55 | self.name = "Network Server Connection Event" 56 | self.server = serverReference 57 | 58 | class ClientConnectEvent(Event): 59 | """this event is generated by the Server whenever a client connects 60 | to it""" 61 | def __init__(self, client): 62 | self.name = "Network Client Connection Event" 63 | self.client = client 64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/example2/example1.py: -------------------------------------------------------------------------------- 1 | def Debug( msg ): 2 | print msg 3 | 4 | DIRECTION_UP = 0 5 | DIRECTION_DOWN = 1 6 | DIRECTION_LEFT = 2 7 | DIRECTION_RIGHT = 3 8 | 9 | from events import * 10 | 11 | #------------------------------------------------------------------------------ 12 | class EventManager: 13 | """this object is responsible for coordinating most communication 14 | between the Model, View, and Controller.""" 15 | def __init__(self ): 16 | from weakref import WeakKeyDictionary 17 | self.listeners = WeakKeyDictionary() 18 | self.eventQueue= [] 19 | 20 | #---------------------------------------------------------------------- 21 | def RegisterListener( self, listener ): 22 | self.listeners[ listener ] = 1 23 | 24 | #---------------------------------------------------------------------- 25 | def UnregisterListener( self, listener ): 26 | if listener in self.listeners: 27 | del self.listeners[ listener ] 28 | 29 | #---------------------------------------------------------------------- 30 | def Post( self, event ): 31 | self.eventQueue.append(event) 32 | if isinstance(event, TickEvent): 33 | # Consume the event queue every Tick. 34 | self.ConsumeEventQueue() 35 | else: 36 | Debug( " Message: " + event.name ) 37 | 38 | #---------------------------------------------------------------------- 39 | def ConsumeEventQueue(self): 40 | i = 0 41 | while i < len( self.eventQueue ): 42 | event = self.eventQueue[i] 43 | for listener in self.listeners: 44 | # Note: a side effect of notifying the listener 45 | # could be that more events are put on the queue 46 | listener.Notify( event ) 47 | i += 1 48 | #all code paths that could possibly add more events to 49 | # the eventQueue have been exhausted at this point, so 50 | # it's safe to empty the queue 51 | self.eventQueue= [] 52 | 53 | 54 | #------------------------------------------------------------------------------ 55 | class KeyboardController: 56 | """...""" 57 | def __init__(self, evManager): 58 | self.evManager = evManager 59 | self.evManager.RegisterListener( self ) 60 | 61 | #---------------------------------------------------------------------- 62 | def Notify(self, event): 63 | if isinstance( event, TickEvent ): 64 | #Handle Input Events 65 | for event in pygame.event.get(): 66 | ev = None 67 | if event.type == QUIT: 68 | ev = QuitEvent() 69 | elif event.type == KEYDOWN \ 70 | and event.key == K_ESCAPE: 71 | ev = QuitEvent() 72 | elif event.type == KEYDOWN \ 73 | and event.key == K_UP: 74 | direction = DIRECTION_UP 75 | ev = CharactorMoveRequest(direction) 76 | elif event.type == KEYDOWN \ 77 | and event.key == K_DOWN: 78 | direction = DIRECTION_DOWN 79 | ev = CharactorMoveRequest(direction) 80 | elif event.type == KEYDOWN \ 81 | and event.key == K_LEFT: 82 | direction = DIRECTION_LEFT 83 | ev = CharactorMoveRequest(direction) 84 | elif event.type == KEYDOWN \ 85 | and event.key == K_RIGHT: 86 | direction = DIRECTION_RIGHT 87 | ev = CharactorMoveRequest(direction) 88 | elif event.type == KEYDOWN: 89 | ev = GameStartRequest() 90 | 91 | if ev: 92 | self.evManager.Post( ev ) 93 | 94 | 95 | #------------------------------------------------------------------------------ 96 | class CPUSpinnerController: 97 | """...""" 98 | def __init__(self, evManager): 99 | self.evManager = evManager 100 | self.evManager.RegisterListener( self ) 101 | 102 | self.keepGoing = 1 103 | 104 | #---------------------------------------------------------------------- 105 | def Run(self): 106 | while self.keepGoing: 107 | event = TickEvent() 108 | self.evManager.Post( event ) 109 | 110 | #---------------------------------------------------------------------- 111 | def Notify(self, event): 112 | if isinstance( event, QuitEvent ): 113 | #this will stop the while loop from running 114 | self.keepGoing = False 115 | 116 | 117 | import pygame 118 | from pygame.locals import * 119 | #------------------------------------------------------------------------------ 120 | class SectorSprite(pygame.sprite.Sprite): 121 | def __init__(self, sector, group=None): 122 | pygame.sprite.Sprite.__init__(self, group) 123 | self.image = pygame.Surface( (128,128) ) 124 | self.image.fill( (0,255,128) ) 125 | 126 | self.sector = sector 127 | 128 | #------------------------------------------------------------------------------ 129 | class CharactorSprite(pygame.sprite.Sprite): 130 | def __init__(self, group=None): 131 | pygame.sprite.Sprite.__init__(self, group) 132 | 133 | charactorSurf = pygame.Surface( (64,64) ) 134 | charactorSurf = charactorSurf.convert_alpha() 135 | charactorSurf.fill((0,0,0,0)) #make transparent 136 | pygame.draw.circle( charactorSurf, (255,0,0), (32,32), 32 ) 137 | self.image = charactorSurf 138 | self.rect = charactorSurf.get_rect() 139 | 140 | self.moveTo = None 141 | 142 | #---------------------------------------------------------------------- 143 | def update(self): 144 | if self.moveTo: 145 | self.rect.center = self.moveTo 146 | self.moveTo = None 147 | 148 | #------------------------------------------------------------------------------ 149 | class PygameView: 150 | """...""" 151 | def __init__(self, evManager): 152 | self.evManager = evManager 153 | self.evManager.RegisterListener( self ) 154 | 155 | pygame.init() 156 | self.window = pygame.display.set_mode( (424,440) ) 157 | pygame.display.set_caption( 'Example Game' ) 158 | self.background = pygame.Surface( self.window.get_size() ) 159 | self.background.fill( (0,0,0) ) 160 | font = pygame.font.Font(None, 30) 161 | textImg = font.render( "Press SPACE BAR to start", 1, (255,0,0)) 162 | self.background.blit( textImg, (0,0) ) 163 | self.window.blit( self.background, (0,0) ) 164 | pygame.display.flip() 165 | 166 | self.backSprites = pygame.sprite.RenderUpdates() 167 | self.frontSprites = pygame.sprite.RenderUpdates() 168 | 169 | 170 | #---------------------------------------------------------------------- 171 | def ShowMap(self, gameMap): 172 | squareRect = pygame.Rect( (-128,10, 128,128 ) ) 173 | 174 | i = 0 175 | for sector in gameMap.sectors: 176 | if i < 3: 177 | squareRect = squareRect.move( 138,0 ) 178 | else: 179 | i = 0 180 | squareRect = squareRect.move( -(138*2), 138 ) 181 | i += 1 182 | newSprite = SectorSprite( sector, self.backSprites ) 183 | newSprite.rect = squareRect 184 | newSprite = None 185 | 186 | #---------------------------------------------------------------------- 187 | def ShowCharactor(self, charactor): 188 | charactorSprite = CharactorSprite( self.frontSprites ) 189 | 190 | sector = charactor.sector 191 | sectorSprite = self.GetSectorSprite( sector ) 192 | charactorSprite.rect.center = sectorSprite.rect.center 193 | 194 | #---------------------------------------------------------------------- 195 | def MoveCharactor(self, charactor): 196 | charactorSprite = self.GetCharactorSprite( charactor ) 197 | 198 | sector = charactor.sector 199 | sectorSprite = self.GetSectorSprite( sector ) 200 | 201 | charactorSprite.moveTo = sectorSprite.rect.center 202 | 203 | #---------------------------------------------------------------------- 204 | def GetCharactorSprite(self, charactor): 205 | #there will be only one 206 | for s in self.frontSprites: 207 | return s 208 | return None 209 | 210 | #---------------------------------------------------------------------- 211 | def GetSectorSprite(self, sector): 212 | for s in self.backSprites: 213 | if hasattr(s, "sector") and s.sector == sector: 214 | return s 215 | 216 | 217 | #---------------------------------------------------------------------- 218 | def Notify(self, event): 219 | if isinstance( event, TickEvent ): 220 | #Draw Everything 221 | self.backSprites.clear( self.window, self.background ) 222 | self.frontSprites.clear( self.window, self.background ) 223 | 224 | self.backSprites.update() 225 | self.frontSprites.update() 226 | 227 | dirtyRects1 = self.backSprites.draw( self.window ) 228 | dirtyRects2 = self.frontSprites.draw( self.window ) 229 | 230 | dirtyRects = dirtyRects1 + dirtyRects2 231 | pygame.display.update( dirtyRects ) 232 | 233 | 234 | elif isinstance( event, MapBuiltEvent ): 235 | gameMap = event.map 236 | self.ShowMap( gameMap ) 237 | 238 | elif isinstance( event, CharactorPlaceEvent ): 239 | self.ShowCharactor( event.charactor ) 240 | 241 | elif isinstance( event, CharactorMoveEvent ): 242 | self.MoveCharactor( event.charactor ) 243 | 244 | 245 | #------------------------------------------------------------------------------ 246 | class Game: 247 | """...""" 248 | 249 | STATE_PREPARING = 0 250 | STATE_RUNNING = 1 251 | STATE_PAUSED = 2 252 | 253 | #---------------------------------------------------------------------- 254 | def __init__(self, evManager): 255 | self.evManager = evManager 256 | self.evManager.RegisterListener( self ) 257 | 258 | self.state = Game.STATE_PREPARING 259 | 260 | self.players = [ Player(evManager) ] 261 | self.map = Map( evManager ) 262 | 263 | #---------------------------------------------------------------------- 264 | def Start(self): 265 | self.map.Build() 266 | self.state = Game.STATE_RUNNING 267 | ev = GameStartedEvent( self ) 268 | self.evManager.Post( ev ) 269 | 270 | #---------------------------------------------------------------------- 271 | def Notify(self, event): 272 | if isinstance( event, GameStartRequest ): 273 | if self.state == Game.STATE_PREPARING: 274 | self.Start() 275 | 276 | #------------------------------------------------------------------------------ 277 | class Player: 278 | """...""" 279 | def __init__(self, evManager): 280 | self.evManager = evManager 281 | #self.evManager.RegisterListener( self ) 282 | 283 | self.charactors = [ Charactor(evManager) ] 284 | 285 | #------------------------------------------------------------------------------ 286 | class Charactor: 287 | """...""" 288 | 289 | STATE_INACTIVE = 0 290 | STATE_ACTIVE = 1 291 | 292 | def __init__(self, evManager): 293 | self.evManager = evManager 294 | self.evManager.RegisterListener( self ) 295 | 296 | self.sector = None 297 | self.state = Charactor.STATE_INACTIVE 298 | 299 | #---------------------------------------------------------------------- 300 | def Move(self, direction): 301 | if self.state == Charactor.STATE_INACTIVE: 302 | return 303 | 304 | if self.sector.MovePossible( direction ): 305 | newSector = self.sector.neighbors[direction] 306 | self.sector = newSector 307 | ev = CharactorMoveEvent( self ) 308 | self.evManager.Post( ev ) 309 | 310 | #---------------------------------------------------------------------- 311 | def Place(self, sector): 312 | self.sector = sector 313 | self.state = Charactor.STATE_ACTIVE 314 | 315 | ev = CharactorPlaceEvent( self ) 316 | self.evManager.Post( ev ) 317 | 318 | #---------------------------------------------------------------------- 319 | def Notify(self, event): 320 | if isinstance( event, GameStartedEvent ): 321 | gameMap = event.game.map 322 | self.Place( gameMap.sectors[gameMap.startSectorIndex] ) 323 | 324 | elif isinstance( event, CharactorMoveRequest ): 325 | self.Move( event.direction ) 326 | 327 | #------------------------------------------------------------------------------ 328 | class Map: 329 | """...""" 330 | 331 | STATE_PREPARING = 0 332 | STATE_BUILT = 1 333 | 334 | 335 | #---------------------------------------------------------------------- 336 | def __init__(self, evManager): 337 | self.evManager = evManager 338 | #self.evManager.RegisterListener( self ) 339 | 340 | self.state = Map.STATE_PREPARING 341 | 342 | self.sectors = range(9) 343 | self.startSectorIndex = 0 344 | 345 | #---------------------------------------------------------------------- 346 | def Build(self): 347 | for i in range(9): 348 | self.sectors[i] = Sector( self.evManager ) 349 | 350 | self.sectors[3].neighbors[DIRECTION_UP] = self.sectors[0] 351 | self.sectors[4].neighbors[DIRECTION_UP] = self.sectors[1] 352 | self.sectors[5].neighbors[DIRECTION_UP] = self.sectors[2] 353 | self.sectors[6].neighbors[DIRECTION_UP] = self.sectors[3] 354 | self.sectors[7].neighbors[DIRECTION_UP] = self.sectors[4] 355 | self.sectors[8].neighbors[DIRECTION_UP] = self.sectors[5] 356 | 357 | self.sectors[0].neighbors[DIRECTION_DOWN] = self.sectors[3] 358 | self.sectors[1].neighbors[DIRECTION_DOWN] = self.sectors[4] 359 | self.sectors[2].neighbors[DIRECTION_DOWN] = self.sectors[5] 360 | self.sectors[3].neighbors[DIRECTION_DOWN] = self.sectors[6] 361 | self.sectors[4].neighbors[DIRECTION_DOWN] = self.sectors[7] 362 | self.sectors[5].neighbors[DIRECTION_DOWN] = self.sectors[8] 363 | 364 | self.sectors[1].neighbors[DIRECTION_LEFT] = self.sectors[0] 365 | self.sectors[2].neighbors[DIRECTION_LEFT] = self.sectors[1] 366 | self.sectors[4].neighbors[DIRECTION_LEFT] = self.sectors[3] 367 | self.sectors[5].neighbors[DIRECTION_LEFT] = self.sectors[4] 368 | self.sectors[7].neighbors[DIRECTION_LEFT] = self.sectors[6] 369 | self.sectors[8].neighbors[DIRECTION_LEFT] = self.sectors[7] 370 | 371 | self.sectors[0].neighbors[DIRECTION_RIGHT] = self.sectors[1] 372 | self.sectors[1].neighbors[DIRECTION_RIGHT] = self.sectors[2] 373 | self.sectors[3].neighbors[DIRECTION_RIGHT] = self.sectors[4] 374 | self.sectors[4].neighbors[DIRECTION_RIGHT] = self.sectors[5] 375 | self.sectors[6].neighbors[DIRECTION_RIGHT] = self.sectors[7] 376 | self.sectors[7].neighbors[DIRECTION_RIGHT] = self.sectors[8] 377 | 378 | self.state = Map.STATE_BUILT 379 | 380 | ev = MapBuiltEvent( self ) 381 | self.evManager.Post( ev ) 382 | 383 | #------------------------------------------------------------------------------ 384 | class Sector: 385 | """...""" 386 | def __init__(self, evManager): 387 | self.evManager = evManager 388 | #self.evManager.RegisterListener( self ) 389 | 390 | self.neighbors = range(4) 391 | 392 | self.neighbors[DIRECTION_UP] = None 393 | self.neighbors[DIRECTION_DOWN] = None 394 | self.neighbors[DIRECTION_LEFT] = None 395 | self.neighbors[DIRECTION_RIGHT] = None 396 | 397 | #---------------------------------------------------------------------- 398 | def MovePossible(self, direction): 399 | if self.neighbors[direction]: 400 | return 1 401 | 402 | 403 | #------------------------------------------------------------------------------ 404 | def main(): 405 | """...""" 406 | evManager = EventManager() 407 | 408 | keybd = KeyboardController( evManager ) 409 | spinner = CPUSpinnerController( evManager ) 410 | pygameView = PygameView( evManager ) 411 | game = Game( evManager ) 412 | 413 | spinner.Run() 414 | 415 | if __name__ == "__main__": 416 | main() 417 | -------------------------------------------------------------------------------- /examples/example2/network.py: -------------------------------------------------------------------------------- 1 | 2 | from example1 import * 3 | from twisted.spread import pb 4 | 5 | # A list of ALL possible events that a server can send to a client 6 | serverToClientEvents = [] 7 | # A list of ALL possible events that a client can send to a server 8 | clientToServerEvents = [] 9 | 10 | #------------------------------------------------------------------------------ 11 | #Mix-In Helper Functions 12 | #------------------------------------------------------------------------------ 13 | def MixInClass( origClass, addClass ): 14 | if addClass not in origClass.__bases__: 15 | origClass.__bases__ += (addClass,) 16 | 17 | #------------------------------------------------------------------------------ 18 | def MixInCopyClasses( someClass ): 19 | MixInClass( someClass, pb.Copyable ) 20 | MixInClass( someClass, pb.RemoteCopy ) 21 | 22 | 23 | 24 | 25 | #------------------------------------------------------------------------------ 26 | #------------------------------------------------------------------------------ 27 | # For each event class, if it is sendable over the network, we have 28 | # to Mix In the "copy classes", or make a replacement event class that is 29 | # copyable 30 | 31 | #------------------------------------------------------------------------------ 32 | # TickEvent 33 | # Direction: don't send. 34 | #The Tick event happens hundreds of times per second. If we think we need 35 | #to send it over the network, we should REALLY re-evaluate our design 36 | 37 | #------------------------------------------------------------------------------ 38 | # QuitEvent 39 | # Direction: Client to Server only 40 | MixInCopyClasses( QuitEvent ) 41 | pb.setUnjellyableForClass(QuitEvent, QuitEvent) 42 | clientToServerEvents.append( QuitEvent ) 43 | 44 | #------------------------------------------------------------------------------ 45 | # GameStartRequest 46 | # Direction: Client to Server only 47 | MixInCopyClasses( GameStartRequest ) 48 | pb.setUnjellyableForClass(GameStartRequest, GameStartRequest) 49 | clientToServerEvents.append( GameStartRequest ) 50 | 51 | #------------------------------------------------------------------------------ 52 | # CharactorMoveRequest 53 | # Direction: Client to Server only 54 | # this has an additional attribute, direction. it is an int, so it's safe 55 | MixInCopyClasses( CharactorMoveRequest ) 56 | pb.setUnjellyableForClass(CharactorMoveRequest, CharactorMoveRequest) 57 | clientToServerEvents.append( CharactorMoveRequest ) 58 | 59 | 60 | #------------------------------------------------------------------------------ 61 | # ServerConnectEvent 62 | # Direction: don't send. 63 | # we don't need to send this over the network. 64 | 65 | #------------------------------------------------------------------------------ 66 | # ClientConnectEvent 67 | # Direction: don't send. 68 | # we don't need to send this over the network. 69 | 70 | 71 | #------------------------------------------------------------------------------ 72 | # GameStartedEvent 73 | # Direction: Server to Client only 74 | class CopyableGameStartedEvent(pb.Copyable, pb.RemoteCopy): 75 | def __init__(self, event, registry): 76 | self.name = "Copyable Game Started Event" 77 | self.gameID = id(event.game) 78 | registry[self.gameID] = event.game 79 | 80 | pb.setUnjellyableForClass(CopyableGameStartedEvent, CopyableGameStartedEvent) 81 | serverToClientEvents.append( CopyableGameStartedEvent ) 82 | 83 | #------------------------------------------------------------------------------ 84 | # MapBuiltEvent 85 | # Direction: Server to Client only 86 | class CopyableMapBuiltEvent( pb.Copyable, pb.RemoteCopy): 87 | def __init__(self, event, registry ): 88 | self.name = "Copyable Map Finished Building Event" 89 | self.mapID = id( event.map ) 90 | registry[self.mapID] = event.map 91 | 92 | pb.setUnjellyableForClass(CopyableMapBuiltEvent, CopyableMapBuiltEvent) 93 | serverToClientEvents.append( CopyableMapBuiltEvent ) 94 | 95 | #------------------------------------------------------------------------------ 96 | # CharactorMoveEvent 97 | # Direction: Server to Client only 98 | class CopyableCharactorMoveEvent( pb.Copyable, pb.RemoteCopy): 99 | def __init__(self, event, registry ): 100 | self.name = "Copyable Charactor Move Event" 101 | self.charactorID = id( event.charactor ) 102 | registry[self.charactorID] = event.charactor 103 | 104 | pb.setUnjellyableForClass(CopyableCharactorMoveEvent, CopyableCharactorMoveEvent) 105 | serverToClientEvents.append( CopyableCharactorMoveEvent ) 106 | 107 | #------------------------------------------------------------------------------ 108 | # CharactorPlaceEvent 109 | # Direction: Server to Client only 110 | class CopyableCharactorPlaceEvent( pb.Copyable, pb.RemoteCopy): 111 | def __init__(self, event, registry ): 112 | self.name = "Copyable Charactor Placement Event" 113 | self.charactorID = id( event.charactor ) 114 | registry[self.charactorID] = event.charactor 115 | 116 | pb.setUnjellyableForClass(CopyableCharactorPlaceEvent, CopyableCharactorPlaceEvent) 117 | serverToClientEvents.append( CopyableCharactorPlaceEvent ) 118 | 119 | 120 | 121 | #------------------------------------------------------------------------------ 122 | class CopyableCharactor: 123 | def getStateToCopy(self): 124 | d = self.__dict__.copy() 125 | del d['evManager'] 126 | d['sector'] = id( self.sector ) 127 | return d 128 | 129 | 130 | 131 | 132 | 133 | def setCopyableState(self, stateDict, registry): 134 | neededObjIDs = [] 135 | success = 1 136 | if not registry.has_key( stateDict['sector'] ): 137 | neededObjIDs.append( stateDict['sector'] ) 138 | success = 0 139 | else: 140 | self.sector = registry[stateDict['sector']] 141 | return [success, neededObjIDs] 142 | 143 | 144 | 145 | MixInClass( Charactor, CopyableCharactor ) 146 | 147 | #------------------------------------------------------------------------------ 148 | class CopyableMap: 149 | def getStateToCopy(self): 150 | sectorIDList = [] 151 | for sect in self.sectors: 152 | sectorIDList.append( id(sect) ) 153 | return {'ninegrid':1, 'sectorIDList':sectorIDList} 154 | 155 | 156 | 157 | 158 | 159 | def setCopyableState(self, stateDict, registry): 160 | neededObjIDs = [] 161 | success = 1 162 | 163 | if self.state != Map.STATE_BUILT: 164 | self.Build() 165 | 166 | for i, sectID in enumerate(stateDict['sectorIDList']): 167 | registry[sectID] = self.sectors[i] 168 | 169 | return [success, neededObjIDs] 170 | 171 | MixInClass( Map, CopyableMap ) 172 | 173 | 174 | -------------------------------------------------------------------------------- /examples/example2/server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ''' 3 | Example server 4 | ''' 5 | 6 | from twisted.spread import pb 7 | from example1 import EventManager, Game 8 | from events import * 9 | import network 10 | 11 | #------------------------------------------------------------------------------ 12 | class NoTickEventManager(EventManager): 13 | '''This subclass of EventManager doesn't wait for a Tick event before 14 | it starts consuming its event queue. The server module doesn't have 15 | a CPUSpinnerController, so Ticks will not get generated. 16 | ''' 17 | def __init__(self): 18 | EventManager.__init__(self) 19 | self._lock = False 20 | def Post(self, event): 21 | EventManager.Post(self,event) 22 | if not self._lock: 23 | self._lock = True 24 | self.ConsumeEventQueue() 25 | self._lock = False 26 | 27 | 28 | 29 | #------------------------------------------------------------------------------ 30 | class NetworkClientController(pb.Root): 31 | """We RECEIVE events from the CLIENT through this object""" 32 | def __init__(self, evManager, sharedObjectRegistry): 33 | self.evManager = evManager 34 | self.evManager.RegisterListener( self ) 35 | self.sharedObjs = sharedObjectRegistry 36 | 37 | #---------------------------------------------------------------------- 38 | def remote_ClientConnect(self, netClient): 39 | #print "CLIENT CONNECT" 40 | ev = ClientConnectEvent( netClient ) 41 | self.evManager.Post( ev ) 42 | return 1 43 | 44 | #---------------------------------------------------------------------- 45 | def remote_GetObjectState(self, objectID): 46 | #print "request for object state", objectID 47 | if not self.sharedObjs.has_key( objectID ): 48 | return [0,0] 49 | objDict = self.sharedObjs[objectID].getStateToCopy() 50 | return [objectID, objDict] 51 | 52 | #---------------------------------------------------------------------- 53 | def remote_EventOverNetwork(self, event): 54 | #print "Server just got an EVENT" + str(event) 55 | self.evManager.Post( event ) 56 | return 1 57 | 58 | #---------------------------------------------------------------------- 59 | def Notify(self, event): 60 | pass 61 | 62 | 63 | #------------------------------------------------------------------------------ 64 | class TextLogView(object): 65 | def __init__(self, evManager): 66 | self.evManager = evManager 67 | self.evManager.RegisterListener( self ) 68 | 69 | #---------------------------------------------------------------------- 70 | def Notify(self, event): 71 | if isinstance( event, TickEvent ): 72 | return 73 | 74 | print 'TEXTLOG <', 75 | 76 | if isinstance( event, CharactorPlaceEvent ): 77 | print event.name, " at ", event.charactor.sector 78 | 79 | elif isinstance( event, CharactorMoveEvent ): 80 | print event.name, " to ", event.charactor.sector 81 | 82 | 83 | #------------------------------------------------------------------------------ 84 | class NetworkClientView(object): 85 | """We SEND events to the CLIENT through this object""" 86 | def __init__(self, evManager, sharedObjectRegistry): 87 | self.evManager = evManager 88 | self.evManager.RegisterListener( self ) 89 | 90 | self.clients = [] 91 | self.sharedObjs = sharedObjectRegistry 92 | 93 | 94 | #---------------------------------------------------------------------- 95 | def Notify(self, event): 96 | if isinstance( event, ClientConnectEvent ): 97 | self.clients.append( event.client ) 98 | 99 | ev = event 100 | 101 | #don't broadcast events that aren't Copyable 102 | if not isinstance( ev, pb.Copyable ): 103 | evName = ev.__class__.__name__ 104 | copyableClsName = "Copyable"+evName 105 | if not hasattr( network, copyableClsName ): 106 | return 107 | copyableClass = getattr( network, copyableClsName ) 108 | ev = copyableClass( ev, self.sharedObjs ) 109 | 110 | if ev.__class__ not in network.serverToClientEvents: 111 | #print "SERVER NOT SENDING: " +str(ev) 112 | return 113 | 114 | #NOTE: this is very "chatty". We could restrict 115 | # the number of clients notified in the future 116 | for client in self.clients: 117 | print "=====server sending: ", str(ev) 118 | remoteCall = client.callRemote("ServerEvent", ev) 119 | 120 | 121 | 122 | 123 | #------------------------------------------------------------------------------ 124 | def main(): 125 | evManager = NoTickEventManager() 126 | sharedObjectRegistry = {} 127 | 128 | log = TextLogView( evManager ) 129 | clientController = NetworkClientController( evManager, sharedObjectRegistry ) 130 | clientView = NetworkClientView( evManager, sharedObjectRegistry ) 131 | game = Game( evManager ) 132 | 133 | from twisted.internet import reactor 134 | reactor.listenTCP( 8000, pb.PBServerFactory(clientController) ) 135 | 136 | reactor.run() 137 | 138 | if __name__ == "__main__": 139 | main() 140 | -------------------------------------------------------------------------------- /examples/example3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/examples/example3.tar.gz -------------------------------------------------------------------------------- /examples/example3/events.py: -------------------------------------------------------------------------------- 1 | #SECURITY NOTE: anything in here can be created simply by sending the 2 | # class name over the network. This is a potential vulnerability 3 | # I wouldn't suggest letting any of these classes DO anything, especially 4 | # things like file system access, or allocating huge amounts of memory 5 | 6 | class Event: 7 | """this is a superclass for any events that might be generated by an 8 | object and sent to the EventManager""" 9 | def __init__(self): 10 | self.name = "Generic Event" 11 | 12 | class TickEvent(Event): 13 | def __init__(self): 14 | self.name = "CPU Tick Event" 15 | 16 | class SecondEvent(Event): 17 | def __init__(self): 18 | self.name = "Clock One Second Event" 19 | 20 | class QuitEvent(Event): 21 | def __init__(self): 22 | self.name = "Program Quit Event" 23 | 24 | class MapBuiltEvent(Event): 25 | def __init__(self, map): 26 | self.name = "Map Finished Building Event" 27 | self.map = map 28 | 29 | class GameStartRequest(Event): 30 | def __init__(self): 31 | self.name = "Game Start Request" 32 | 33 | class GameStartedEvent(Event): 34 | def __init__(self, game): 35 | self.name = "Game Started Event" 36 | self.game = game 37 | 38 | class CharactorMoveRequest(Event): 39 | def __init__(self, direction): 40 | self.name = "Charactor Move Request" 41 | self.direction = direction 42 | 43 | class CharactorMoveEvent(Event): 44 | def __init__(self, charactor): 45 | self.name = "Charactor Move Event" 46 | self.charactor = charactor 47 | 48 | class CharactorPlaceEvent(Event): 49 | """this event occurs when a Charactor is *placed* in a sector, 50 | ie it doesn't move there from an adjacent sector.""" 51 | def __init__(self, charactor): 52 | self.name = "Charactor Placement Event" 53 | self.charactor = charactor 54 | 55 | class ServerConnectEvent(Event): 56 | """the client generates this when it detects that it has successfully 57 | connected to the server""" 58 | def __init__(self, serverReference): 59 | self.name = "Network Server Connection Event" 60 | self.server = serverReference 61 | 62 | class ClientConnectEvent(Event): 63 | """this event is generated by the Server whenever a client connects 64 | to it""" 65 | def __init__(self, client): 66 | self.name = "Network Client Connection Event" 67 | self.client = client 68 | 69 | class ClientDisconnectEvent(Event): 70 | """this event is generated by the Server when it finds that a client 71 | is no longer connected""" 72 | def __init__(self, client): 73 | self.name = "Network Client Disconnection Event" 74 | self.client = client 75 | 76 | class GameSyncEvent(Event): 77 | """...""" 78 | def __init__(self, game): 79 | self.name = "Game Synched to Authoritative State" 80 | self.game = game 81 | -------------------------------------------------------------------------------- /examples/example3/network.py: -------------------------------------------------------------------------------- 1 | 2 | from example1 import * 3 | from twisted.spread import pb 4 | 5 | # A list of ALL possible events that a server can send to a client 6 | serverToClientEvents = [] 7 | # A list of ALL possible events that a client can send to a server 8 | clientToServerEvents = [] 9 | 10 | #------------------------------------------------------------------------------ 11 | #Mix-In Helper Functions 12 | #------------------------------------------------------------------------------ 13 | def MixInClass( origClass, addClass ): 14 | if addClass not in origClass.__bases__: 15 | origClass.__bases__ += (addClass,) 16 | 17 | #------------------------------------------------------------------------------ 18 | def MixInCopyClasses( someClass ): 19 | MixInClass( someClass, pb.Copyable ) 20 | MixInClass( someClass, pb.RemoteCopy ) 21 | 22 | 23 | 24 | 25 | #------------------------------------------------------------------------------ 26 | #------------------------------------------------------------------------------ 27 | # For each event class, if it is sendable over the network, we have 28 | # to Mix In the "copy classes", or make a replacement event class that is 29 | # copyable 30 | 31 | #------------------------------------------------------------------------------ 32 | # TickEvent 33 | # Direction: don't send. 34 | #The Tick event happens hundreds of times per second. If we think we need 35 | #to send it over the network, we should REALLY re-evaluate our design 36 | 37 | #------------------------------------------------------------------------------ 38 | # QuitEvent 39 | # Direction: Client to Server only 40 | MixInCopyClasses( QuitEvent ) 41 | pb.setUnjellyableForClass(QuitEvent, QuitEvent) 42 | clientToServerEvents.append( QuitEvent ) 43 | 44 | #------------------------------------------------------------------------------ 45 | # GameStartRequest 46 | # Direction: Client to Server only 47 | MixInCopyClasses( GameStartRequest ) 48 | pb.setUnjellyableForClass(GameStartRequest, GameStartRequest) 49 | clientToServerEvents.append( GameStartRequest ) 50 | 51 | #------------------------------------------------------------------------------ 52 | # CharactorMoveRequest 53 | # Direction: Client to Server only 54 | # this has an additional attribute, direction. it is an int, so it's safe 55 | MixInCopyClasses( CharactorMoveRequest ) 56 | pb.setUnjellyableForClass(CharactorMoveRequest, CharactorMoveRequest) 57 | clientToServerEvents.append( CharactorMoveRequest ) 58 | 59 | 60 | #------------------------------------------------------------------------------ 61 | # ServerConnectEvent 62 | # Direction: don't send. 63 | # we don't need to send this over the network. 64 | 65 | #------------------------------------------------------------------------------ 66 | # ClientConnectEvent 67 | # Direction: don't send. 68 | # we don't need to send this over the network. 69 | 70 | 71 | #------------------------------------------------------------------------------ 72 | # GameStartedEvent 73 | # Direction: Server to Client only 74 | class CopyableGameStartedEvent(pb.Copyable, pb.RemoteCopy): 75 | def __init__(self, event, registry): 76 | self.name = "Copyable Game Started Event" 77 | self.gameID = id(event.game) 78 | registry[self.gameID] = event.game 79 | #TODO: put this in a Player Join Event or something 80 | for p in event.game.players: 81 | registry[id(p)] = p 82 | 83 | pb.setUnjellyableForClass(CopyableGameStartedEvent, CopyableGameStartedEvent) 84 | serverToClientEvents.append( CopyableGameStartedEvent ) 85 | 86 | #------------------------------------------------------------------------------ 87 | # MapBuiltEvent 88 | # Direction: Server to Client only 89 | class CopyableMapBuiltEvent( pb.Copyable, pb.RemoteCopy): 90 | def __init__(self, event, registry ): 91 | self.name = "Copyable Map Finished Building Event" 92 | self.mapID = id( event.map ) 93 | registry[self.mapID] = event.map 94 | 95 | pb.setUnjellyableForClass(CopyableMapBuiltEvent, CopyableMapBuiltEvent) 96 | serverToClientEvents.append( CopyableMapBuiltEvent ) 97 | 98 | #------------------------------------------------------------------------------ 99 | # CharactorMoveEvent 100 | # Direction: Server to Client only 101 | class CopyableCharactorMoveEvent( pb.Copyable, pb.RemoteCopy): 102 | def __init__(self, event, registry ): 103 | self.name = "Copyable Charactor Move Event" 104 | self.charactorID = id( event.charactor ) 105 | registry[self.charactorID] = event.charactor 106 | 107 | pb.setUnjellyableForClass(CopyableCharactorMoveEvent, CopyableCharactorMoveEvent) 108 | serverToClientEvents.append( CopyableCharactorMoveEvent ) 109 | 110 | #------------------------------------------------------------------------------ 111 | # CharactorPlaceEvent 112 | # Direction: Server to Client only 113 | class CopyableCharactorPlaceEvent( pb.Copyable, pb.RemoteCopy): 114 | def __init__(self, event, registry ): 115 | self.name = "Copyable Charactor Placement Event" 116 | self.charactorID = id( event.charactor ) 117 | registry[self.charactorID] = event.charactor 118 | 119 | pb.setUnjellyableForClass(CopyableCharactorPlaceEvent, CopyableCharactorPlaceEvent) 120 | serverToClientEvents.append( CopyableCharactorPlaceEvent ) 121 | 122 | 123 | 124 | #------------------------------------------------------------------------------ 125 | class CopyableCharactor: 126 | def getStateToCopy(self, registry): 127 | d = self.__dict__.copy() 128 | del d['evManager'] 129 | 130 | sID = id( self.sector ) 131 | d['sector'] = sID 132 | registry[sID] = self.sector 133 | 134 | return d 135 | 136 | def setCopyableState(self, stateDict, registry): 137 | neededObjIDs = [] 138 | success = 1 139 | if stateDict['sector'] not in registry: 140 | registry[stateDict['sector']] = Sector(self.evManager) 141 | neededObjIDs.append( stateDict['sector'] ) 142 | success = 0 143 | else: 144 | self.sector = registry[stateDict['sector']] 145 | return [success, neededObjIDs] 146 | 147 | 148 | MixInClass( Charactor, CopyableCharactor ) 149 | 150 | #------------------------------------------------------------------------------ 151 | class CopyableMap: 152 | def getStateToCopy(self, registry): 153 | sectorIDList = [] 154 | for sect in self.sectors: 155 | sID = id(sect) 156 | sectorIDList.append( sID ) 157 | registry[sID] = sect 158 | 159 | return {'ninegrid':1, 'sectorIDList':sectorIDList} 160 | 161 | 162 | def setCopyableState(self, stateDict, registry): 163 | neededObjIDs = [] 164 | success = 1 165 | 166 | if self.state != Map.STATE_BUILT: 167 | self.Build() 168 | 169 | for i, sectID in enumerate(stateDict['sectorIDList']): 170 | registry[sectID] = self.sectors[i] 171 | 172 | return [success, neededObjIDs] 173 | 174 | MixInClass( Map, CopyableMap ) 175 | 176 | #------------------------------------------------------------------------------ 177 | class CopyableGame: 178 | def getStateToCopy(self, registry): 179 | d = self.__dict__.copy() 180 | del d['evManager'] 181 | 182 | mID = id( self.map ) 183 | d['map'] = mID 184 | registry[mID] = self.map 185 | 186 | playerIDList = [] 187 | for player in self.players: 188 | pID = id( player ) 189 | playerIDList.append( pID ) 190 | registry[pID] = player 191 | d['players'] = playerIDList 192 | 193 | return d 194 | 195 | def setCopyableState(self, stateDict, registry): 196 | neededObjIDs = [] 197 | success = 1 198 | 199 | if stateDict['map'] not in registry: 200 | registry[stateDict['map']] = Map( self.evManager ) 201 | neededObjIDs.append( stateDict['map'] ) 202 | success = 0 203 | else: 204 | self.map = registry[stateDict['map']] 205 | 206 | for pID in stateDict['players']: 207 | if pID not in registry: 208 | registry[pID] = Player( self.evManager ) 209 | neededObjIDs.append( pID ) 210 | success = 0 211 | else: 212 | self.players.append( registry[pID] ) 213 | 214 | return [success, neededObjIDs] 215 | 216 | MixInClass( Game, CopyableGame ) 217 | 218 | #------------------------------------------------------------------------------ 219 | class CopyablePlayer: 220 | def getStateToCopy(self, registry): 221 | d = self.__dict__.copy() 222 | del d['evManager'] 223 | 224 | charactorIDList = [] 225 | for charactor in self.charactors: 226 | cID = id( charactor ) 227 | charactorIDList.append( cID ) 228 | registry[cID] = charactor 229 | d['charactors'] = charactorIDList 230 | 231 | return d 232 | 233 | def setCopyableState(self, stateDict, registry): 234 | neededObjIDs = [] 235 | success = 1 236 | 237 | for cID in stateDict['charactors']: 238 | if not cID in registry: 239 | registry[cID] = Charactor( self.evManager ) 240 | neededObjIDs.append( cID ) 241 | success = 0 242 | else: 243 | self.charactors.append( registry[cID] ) 244 | 245 | return [success, neededObjIDs] 246 | 247 | MixInClass( Player, CopyablePlayer ) 248 | -------------------------------------------------------------------------------- /examples/example3/server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ''' 3 | Example server 4 | ''' 5 | 6 | from twisted.spread import pb 7 | from twisted.spread.pb import DeadReferenceError 8 | from example1 import EventManager, Game 9 | from events import * 10 | import network 11 | 12 | #------------------------------------------------------------------------------ 13 | class NoTickEventManager(EventManager): 14 | '''This subclass of EventManager doesn't wait for a Tick event before 15 | it starts consuming its event queue. The server module doesn't have 16 | a CPUSpinnerController, so Ticks will not get generated. 17 | ''' 18 | def __init__(self): 19 | EventManager.__init__(self) 20 | self._lock = False 21 | def Post(self, event): 22 | self.eventQueue.append(event) 23 | if not self._lock: 24 | self._lock = True 25 | self.ActuallyUpdateListeners() 26 | self.ConsumeEventQueue() 27 | self._lock = False 28 | 29 | 30 | 31 | #------------------------------------------------------------------------------ 32 | class TimerController: 33 | """A controller that sends of an event every second""" 34 | def __init__(self, evManager, reactor): 35 | self.evManager = evManager 36 | self.evManager.RegisterListener( self ) 37 | 38 | self.reactor = reactor 39 | self.numClients = 0 40 | 41 | #----------------------------------------------------------------------- 42 | def NotifyApplicationStarted( self ): 43 | self.reactor.callLater( 1, self.Tick ) 44 | 45 | #----------------------------------------------------------------------- 46 | def Tick(self): 47 | if self.numClients == 0: 48 | return 49 | 50 | ev = SecondEvent() 51 | self.evManager.Post( ev ) 52 | ev = TickEvent() 53 | self.evManager.Post( ev ) 54 | self.reactor.callLater( 1, self.Tick ) 55 | 56 | #---------------------------------------------------------------------- 57 | def Notify(self, event): 58 | if isinstance( event, ClientConnectEvent ): 59 | self.numClients += 1 60 | if self.numClients == 1: 61 | self.Tick() 62 | if isinstance( event, ClientDisconnectEvent ): 63 | self.numClients -= 1 64 | 65 | #------------------------------------------------------------------------------ 66 | class NetworkClientController(pb.Root): 67 | """We RECEIVE events from the CLIENT through this object""" 68 | def __init__(self, evManager, sharedObjectRegistry): 69 | self.evManager = evManager 70 | self.evManager.RegisterListener( self ) 71 | self.sharedObjs = sharedObjectRegistry 72 | 73 | #this is needed for GetEntireState() 74 | self.game = None 75 | 76 | #---------------------------------------------------------------------- 77 | def remote_ClientConnect(self, netClient): 78 | print "\nremote_CLIENT CONNECT" 79 | ev = ClientConnectEvent( netClient ) 80 | self.evManager.Post( ev ) 81 | if self.game == None: 82 | gameID = 0 83 | else: 84 | gameID = id(self.game) 85 | return gameID 86 | 87 | #---------------------------------------------------------------------- 88 | def remote_GetGame(self): 89 | """this is usually called when a client first connects or 90 | when they had dropped and reconnect""" 91 | if self.game == None: 92 | return [0,0] 93 | gameID = id( self.game ) 94 | gameDict = self.game.getStateToCopy( self.sharedObjs ) 95 | 96 | print "returning: ", gameID 97 | return [gameID, gameDict] 98 | 99 | #---------------------------------------------------------------------- 100 | def remote_GetObjectState(self, objectID): 101 | #print "request for object state", objectID 102 | if not self.sharedObjs.has_key( objectID ): 103 | return [0,0] 104 | obj = self.sharedObjs[objectID] 105 | objDict = obj.getStateToCopy( self.sharedObjs ) 106 | 107 | return [objectID, objDict] 108 | 109 | #---------------------------------------------------------------------- 110 | def remote_EventOverNetwork(self, event): 111 | #print "Server just got an EVENT" + str(event) 112 | self.evManager.Post( event ) 113 | return 1 114 | 115 | #---------------------------------------------------------------------- 116 | def Notify(self, event): 117 | if isinstance( event, GameStartedEvent ): 118 | self.game = event.game 119 | 120 | 121 | #------------------------------------------------------------------------------ 122 | class TextLogView(object): 123 | def __init__(self, evManager): 124 | self.evManager = evManager 125 | self.evManager.RegisterListener( self ) 126 | 127 | #---------------------------------------------------------------------- 128 | def Notify(self, event): 129 | if isinstance( event, TickEvent ): 130 | return 131 | 132 | print 'TEXTLOG <', 133 | 134 | if isinstance( event, CharactorPlaceEvent ): 135 | print event.name, " at ", event.charactor.sector 136 | 137 | elif isinstance( event, CharactorMoveEvent ): 138 | print event.name, " to ", event.charactor.sector 139 | else: 140 | print 'event:', event 141 | 142 | 143 | #------------------------------------------------------------------------------ 144 | class NetworkClientView(object): 145 | """We SEND events to the CLIENT through this object""" 146 | def __init__(self, evManager, sharedObjectRegistry): 147 | self.evManager = evManager 148 | self.evManager.RegisterListener( self ) 149 | 150 | self.clients = [] 151 | self.sharedObjs = sharedObjectRegistry 152 | #TODO: 153 | #every 5 seconds, the server should poll the clients to see if 154 | # they're still connected 155 | self.pollSeconds = 0 156 | 157 | #---------------------------------------------------------------------- 158 | def Pong(self ): 159 | pass 160 | 161 | #---------------------------------------------------------------------- 162 | def RemoteCallError(self, failure, client): 163 | from twisted.internet.error import ConnectionLost 164 | #trap ensures that the rest will happen only 165 | #if the failure was ConnectionLost 166 | failure.trap(ConnectionLost) 167 | self.DisconnectClient(client) 168 | return failure 169 | 170 | #---------------------------------------------------------------------- 171 | def DisconnectClient(self, client): 172 | print "Disconnecting Client", client 173 | self.clients.remove( client ) 174 | ev = ClientDisconnectEvent( client ) #client id in here 175 | self.evManager.Post( ev ) 176 | 177 | #---------------------------------------------------------------------- 178 | def RemoteCall( self, client, fnName, *args): 179 | 180 | try: 181 | remoteCall = client.callRemote(fnName, *args) 182 | #remoteCall.addCallback( self.Pong ) 183 | remoteCall.addErrback( self.RemoteCallError, client ) 184 | except DeadReferenceError: 185 | self.DisconnectClient(client) 186 | 187 | 188 | #---------------------------------------------------------------------- 189 | def Notify(self, event): 190 | if isinstance( event, ClientConnectEvent ): 191 | print "\nADDING CLIENT", event.client 192 | self.clients.append( event.client ) 193 | #TODO tell the client what it's ID is 194 | 195 | if isinstance( event, SecondEvent ): 196 | self.pollSeconds +=1 197 | if self.pollSeconds == 10: 198 | self.pollSeconds = 0 199 | for client in self.clients: 200 | self.RemoteCall( client, "Ping" ) 201 | 202 | 203 | ev = event 204 | 205 | #don't broadcast events that aren't Copyable 206 | if not isinstance( ev, pb.Copyable ): 207 | evName = ev.__class__.__name__ 208 | copyableClsName = "Copyable"+evName 209 | if not hasattr( network, copyableClsName ): 210 | return 211 | copyableClass = getattr( network, copyableClsName ) 212 | ev = copyableClass( ev, self.sharedObjs ) 213 | 214 | if ev.__class__ not in network.serverToClientEvents: 215 | #print "SERVER NOT SENDING: " +str(ev) 216 | return 217 | 218 | #NOTE: this is very "chatty". We could restrict 219 | # the number of clients notified in the future 220 | for client in self.clients: 221 | print "\n====server===sending: ", str(ev), 'to', client 222 | self.RemoteCall( client, "ServerEvent", ev ) 223 | 224 | 225 | 226 | 227 | #------------------------------------------------------------------------------ 228 | def main(): 229 | from twisted.internet import reactor 230 | evManager = NoTickEventManager() 231 | sharedObjectRegistry = {} 232 | 233 | log = TextLogView( evManager ) 234 | timer = TimerController( evManager, reactor ) 235 | clientController = NetworkClientController( evManager, sharedObjectRegistry ) 236 | clientView = NetworkClientView( evManager, sharedObjectRegistry ) 237 | game = Game( evManager ) 238 | 239 | reactor.listenTCP( 8000, pb.PBServerFactory(clientController) ) 240 | 241 | reactor.run() 242 | 243 | if __name__ == "__main__": 244 | main() 245 | -------------------------------------------------------------------------------- /examples/example4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/examples/example4.tar.gz -------------------------------------------------------------------------------- /examples/example4/events.py: -------------------------------------------------------------------------------- 1 | #SECURITY NOTE: anything in here can be created simply by sending the 2 | # class name over the network. This is a potential vulnerability 3 | # I wouldn't suggest letting any of these classes DO anything, especially 4 | # things like file system access, or allocating huge amounts of memory 5 | 6 | class Event: 7 | """this is a superclass for any events that might be generated by an 8 | object and sent to the EventManager""" 9 | def __init__(self): 10 | self.name = "Generic Event" 11 | def __str__(self): 12 | return '<%s %s>' % (self.__class__.__name__, 13 | id(self)) 14 | 15 | 16 | class TickEvent(Event): 17 | def __init__(self): 18 | self.name = "CPU Tick Event" 19 | 20 | class SecondEvent(Event): 21 | def __init__(self): 22 | self.name = "Clock One Second Event" 23 | 24 | class QuitEvent(Event): 25 | def __init__(self): 26 | self.name = "Program Quit Event" 27 | 28 | class FatalEvent(Event): 29 | def __init__(self, *args): 30 | self.name = "Fatal Error Event" 31 | self.args = args 32 | 33 | class MapBuiltEvent(Event): 34 | def __init__(self, map): 35 | self.name = "Map Finished Building Event" 36 | self.map = map 37 | 38 | class GameStartRequest(Event): 39 | def __init__(self): 40 | self.name = "Game Start Request" 41 | 42 | class GameStartedEvent(Event): 43 | def __init__(self, game): 44 | self.name = "Game Started Event" 45 | self.game = game 46 | 47 | class CharactorMoveRequest(Event): 48 | def __init__(self, player, charactor, direction): 49 | self.name = "Charactor Move Request" 50 | self.player = player 51 | self.charactor = charactor 52 | self.direction = direction 53 | 54 | class CharactorMoveEvent(Event): 55 | def __init__(self, charactor): 56 | self.name = "Charactor Move Event" 57 | self.charactor = charactor 58 | 59 | class CharactorPlaceEvent(Event): 60 | """this event occurs when a Charactor is *placed* in a sector, 61 | ie it doesn't move there from an adjacent sector.""" 62 | def __init__(self, charactor): 63 | self.name = "Charactor Placement Event" 64 | self.charactor = charactor 65 | 66 | class ServerConnectEvent(Event): 67 | """the client generates this when it detects that it has successfully 68 | connected to the server""" 69 | def __init__(self, serverReference): 70 | self.name = "Network Server Connection Event" 71 | self.server = serverReference 72 | 73 | class ClientConnectEvent(Event): 74 | """this event is generated by the Server whenever a client connects 75 | to it""" 76 | def __init__(self, client, avatarID): 77 | self.name = "Network Client Connection Event" 78 | self.client = client 79 | self.avatarID = avatarID 80 | 81 | class ClientDisconnectEvent(Event): 82 | """this event is generated by the Server when it finds that a client 83 | is no longer connected""" 84 | def __init__(self, avatarID): 85 | self.name = "Network Client Disconnection Event" 86 | self.avatarID = avatarID 87 | 88 | class GameSyncEvent(Event): 89 | """...""" 90 | def __init__(self, game): 91 | self.name = "Game Synched to Authoritative State" 92 | self.game = game 93 | 94 | class PlayerJoinRequest(Event): 95 | """...""" 96 | def __init__(self, playerDict): 97 | self.name = "Player Joining Game Request" 98 | self.playerDict = playerDict 99 | 100 | class PlayerJoinEvent(Event): 101 | """...""" 102 | def __init__(self, player): 103 | self.name = "Player Joined Game Event" 104 | self.player = player 105 | 106 | class CharactorPlaceRequest(Event): 107 | """...""" 108 | def __init__(self, player, charactor, sector): 109 | self.name = "Charactor Placement Request" 110 | self.player = player 111 | self.charactor = charactor 112 | self.sector = sector 113 | -------------------------------------------------------------------------------- /examples/example4/network.py: -------------------------------------------------------------------------------- 1 | 2 | from example1 import * 3 | from twisted.spread import pb 4 | 5 | # A list of ALL possible events that a server can send to a client 6 | serverToClientEvents = [] 7 | # A list of ALL possible events that a client can send to a server 8 | clientToServerEvents = [] 9 | 10 | #------------------------------------------------------------------------------ 11 | #Mix-In Helper Functions 12 | #------------------------------------------------------------------------------ 13 | def MixInClass( origClass, addClass ): 14 | if addClass not in origClass.__bases__: 15 | origClass.__bases__ += (addClass,) 16 | 17 | #------------------------------------------------------------------------------ 18 | def MixInCopyClasses( someClass ): 19 | MixInClass( someClass, pb.Copyable ) 20 | MixInClass( someClass, pb.RemoteCopy ) 21 | 22 | #------------------------------------------------------------------------------ 23 | def serialize(obj, registry): 24 | objType = type(obj) 25 | if objType in [str, unicode, int, float, bool, type(None)]: 26 | return obj 27 | 28 | elif objType in [list, tuple]: 29 | new_obj = [] 30 | for sub_obj in obj: 31 | new_obj.append(serialize(sub_obj, registry)) 32 | return new_obj 33 | 34 | elif objType == dict: 35 | new_obj = {} 36 | for key, val in obj.items(): 37 | new_obj[serialize(key, registry)] = serialize(val, registry) 38 | return new_obj 39 | 40 | else: 41 | objID = id(obj) 42 | registry[objID] = obj 43 | return objID 44 | 45 | #------------------------------------------------------------------------------ 46 | class Serializable: 47 | '''The Serializable interface. 48 | All objects inheriting Serializable must have a .copyworthy_attrs member. 49 | ''' 50 | def getStateToCopy(self, registry): 51 | d = {} 52 | for attr in self.copyworthy_attrs: 53 | val = getattr(self, attr) 54 | new_val = serialize(val, registry) 55 | d[attr] = new_val 56 | 57 | return d 58 | 59 | 60 | #------------------------------------------------------------------------------ 61 | #------------------------------------------------------------------------------ 62 | # For each event class, if it is sendable over the network, we have 63 | # to Mix In the "copy classes", or make a replacement event class that is 64 | # copyable 65 | 66 | #------------------------------------------------------------------------------ 67 | # TickEvent 68 | # Direction: don't send. 69 | #The Tick event happens hundreds of times per second. If we think we need 70 | #to send it over the network, we should REALLY re-evaluate our design 71 | 72 | #------------------------------------------------------------------------------ 73 | # QuitEvent 74 | # Direction: Client to Server only 75 | MixInCopyClasses( QuitEvent ) 76 | pb.setUnjellyableForClass(QuitEvent, QuitEvent) 77 | clientToServerEvents.append( QuitEvent ) 78 | 79 | #------------------------------------------------------------------------------ 80 | # GameStartRequest 81 | # Direction: Client to Server only 82 | MixInCopyClasses( GameStartRequest ) 83 | pb.setUnjellyableForClass(GameStartRequest, GameStartRequest) 84 | clientToServerEvents.append( GameStartRequest ) 85 | 86 | 87 | 88 | #------------------------------------------------------------------------------ 89 | # ServerConnectEvent 90 | # Direction: don't send. 91 | # we don't need to send this over the network. 92 | 93 | #------------------------------------------------------------------------------ 94 | # ClientConnectEvent 95 | # Direction: don't send. 96 | # we don't need to send this over the network. 97 | 98 | #------------------------------------------------------------------------------ 99 | class ServerErrorEvent(object): 100 | def __init__(self): 101 | self.name = "Server Err Event" 102 | 103 | #------------------------------------------------------------------------------ 104 | class ClientErrorEvent(object): 105 | def __init__(self): 106 | self.name = "Client Err Event" 107 | 108 | #------------------------------------------------------------------------------ 109 | # GameStartedEvent 110 | # Direction: Server to Client only 111 | class CopyableGameStartedEvent(pb.Copyable, pb.RemoteCopy): 112 | def __init__(self, event, registry): 113 | self.name = "Copyable Game Started Event" 114 | self.gameID = id(event.game) 115 | registry[self.gameID] = event.game 116 | #TODO: put this in a Player Join Event or something 117 | for p in event.game.players: 118 | registry[id(p)] = p 119 | 120 | pb.setUnjellyableForClass(CopyableGameStartedEvent, CopyableGameStartedEvent) 121 | serverToClientEvents.append( CopyableGameStartedEvent ) 122 | 123 | #------------------------------------------------------------------------------ 124 | # MapBuiltEvent 125 | # Direction: Server to Client only 126 | class CopyableMapBuiltEvent( pb.Copyable, pb.RemoteCopy): 127 | def __init__(self, event, registry ): 128 | self.name = "Copyable Map Finished Building Event" 129 | self.mapID = id( event.map ) 130 | registry[self.mapID] = event.map 131 | 132 | pb.setUnjellyableForClass(CopyableMapBuiltEvent, CopyableMapBuiltEvent) 133 | serverToClientEvents.append( CopyableMapBuiltEvent ) 134 | 135 | #------------------------------------------------------------------------------ 136 | # CharactorMoveEvent 137 | # Direction: Server to Client only 138 | class CopyableCharactorMoveEvent( pb.Copyable, pb.RemoteCopy): 139 | def __init__(self, event, registry ): 140 | self.name = "Copyable " + event.name 141 | self.charactorID = id( event.charactor ) 142 | registry[self.charactorID] = event.charactor 143 | 144 | pb.setUnjellyableForClass(CopyableCharactorMoveEvent, CopyableCharactorMoveEvent) 145 | serverToClientEvents.append( CopyableCharactorMoveEvent ) 146 | 147 | #------------------------------------------------------------------------------ 148 | # CharactorPlaceEvent 149 | # Direction: Server to Client only 150 | class CopyableCharactorPlaceEvent( pb.Copyable, pb.RemoteCopy): 151 | def __init__(self, event, registry ): 152 | self.name = "Copyable " + event.name 153 | self.charactorID = id( event.charactor ) 154 | registry[self.charactorID] = event.charactor 155 | 156 | pb.setUnjellyableForClass(CopyableCharactorPlaceEvent, CopyableCharactorPlaceEvent) 157 | serverToClientEvents.append( CopyableCharactorPlaceEvent ) 158 | 159 | 160 | #------------------------------------------------------------------------------ 161 | # PlayerJoinRequest 162 | # Direction: Client to Server only 163 | MixInCopyClasses( PlayerJoinRequest ) 164 | pb.setUnjellyableForClass(PlayerJoinRequest, PlayerJoinRequest) 165 | clientToServerEvents.append( PlayerJoinRequest ) 166 | 167 | #------------------------------------------------------------------------------ 168 | # PlayerJoinEvent 169 | # Direction: Server to Client only 170 | class CopyablePlayerJoinEvent( pb.Copyable, pb.RemoteCopy): 171 | def __init__(self, event, registry): 172 | self.name = "Copyable " + event.name 173 | self.playerID = id(event.player) 174 | registry[self.playerID] = event.player 175 | pb.setUnjellyableForClass(CopyablePlayerJoinEvent, CopyablePlayerJoinEvent) 176 | serverToClientEvents.append( CopyablePlayerJoinEvent ) 177 | 178 | #------------------------------------------------------------------------------ 179 | # CharactorPlaceRequest 180 | # Direction: Client to Server only 181 | class CopyableCharactorPlaceRequest( pb.Copyable, pb.RemoteCopy): 182 | def __init__(self, event, registry ): 183 | self.name = "Copyable " + event.name 184 | self.playerID = None 185 | self.charactorID = None 186 | self.sectorID = None 187 | for key,val in registry.iteritems(): 188 | if val is event.player: 189 | print 'making char place request' 190 | print 'self.playerid', key 191 | self.playerID = key 192 | if val is event.charactor: 193 | self.charactorID = key 194 | if val is event.sector: 195 | self.sectorID = key 196 | if None in ( self.playerID, self.charactorID, self.sectorID): 197 | print "SOMETHING REALLY WRONG" 198 | print self.playerID, event.player 199 | print self.charactorID, event.charactor 200 | print self.sectorID, event.sector 201 | pb.setUnjellyableForClass(CopyableCharactorPlaceRequest, CopyableCharactorPlaceRequest) 202 | clientToServerEvents.append( CopyableCharactorPlaceRequest ) 203 | 204 | #------------------------------------------------------------------------------ 205 | # CharactorMoveRequest 206 | # Direction: Client to Server only 207 | class CopyableCharactorMoveRequest( pb.Copyable, pb.RemoteCopy): 208 | def __init__(self, event, registry ): 209 | self.name = "Copyable " + event.name 210 | self.direction = event.direction 211 | self.playerID = None 212 | self.charactorID = None 213 | for key,val in registry.iteritems(): 214 | if val is event.player: 215 | self.playerID = key 216 | if val is event.charactor: 217 | self.charactorID = key 218 | if None in ( self.playerID, self.charactorID): 219 | print "SOMETHING REALLY WRONG" 220 | print self.playerID, event.player 221 | print self.charactorID, event.charactor 222 | pb.setUnjellyableForClass(CopyableCharactorMoveRequest, CopyableCharactorMoveRequest) 223 | clientToServerEvents.append( CopyableCharactorMoveRequest ) 224 | 225 | #------------------------------------------------------------------------------ 226 | #------------------------------------------------------------------------------ 227 | # For any objects that we need to send in our events, we have to give them 228 | # getStateToCopy() and setCopyableState() methods so that we can send a 229 | # network-friendly representation of them over the network. 230 | 231 | #------------------------------------------------------------------------------ 232 | class CopyableMap: 233 | def getStateToCopy(self, registry): 234 | sectorIDList = [] 235 | for sect in self.sectors: 236 | sID = id(sect) 237 | sectorIDList.append( sID ) 238 | registry[sID] = sect 239 | 240 | return {'ninegrid':1, 'sectorIDList':sectorIDList} 241 | 242 | 243 | def setCopyableState(self, stateDict, registry): 244 | neededObjIDs = [] 245 | success = True 246 | 247 | if self.state != Map.STATE_BUILT: 248 | self.Build() 249 | 250 | for i, sectID in enumerate(stateDict['sectorIDList']): 251 | registry[sectID] = self.sectors[i] 252 | 253 | return [success, neededObjIDs] 254 | 255 | MixInClass( Map, CopyableMap ) 256 | 257 | 258 | #------------------------------------------------------------------------------ 259 | class CopyableGame(Serializable): 260 | copyworthy_attrs = ['map', 'state', 'players'] 261 | 262 | def setCopyableState(self, stateDict, registry): 263 | neededObjIDs = [] 264 | success = True 265 | 266 | self.state = stateDict['state'] 267 | 268 | if not registry.has_key( stateDict['map'] ): 269 | registry[stateDict['map']] = Map( self.evManager ) 270 | neededObjIDs.append( stateDict['map'] ) 271 | success = False 272 | else: 273 | self.map = registry[stateDict['map']] 274 | 275 | self.players = [] 276 | for pID in stateDict['players']: 277 | if not registry.has_key( pID ): 278 | registry[pID] = Player( self.evManager ) 279 | neededObjIDs.append( pID ) 280 | success = False 281 | else: 282 | self.players.append( registry[pID] ) 283 | 284 | return [success, neededObjIDs] 285 | 286 | MixInClass( Game, CopyableGame ) 287 | 288 | #------------------------------------------------------------------------------ 289 | class CopyablePlayer(Serializable): 290 | copyworthy_attrs = ['name', 'game', 'charactors'] 291 | 292 | def setCopyableState(self, stateDict, registry): 293 | neededObjIDs = [] 294 | success = True 295 | 296 | self.name = stateDict['name'] 297 | 298 | if not registry.has_key( stateDict['game'] ): 299 | print "Something is wrong. should already be a game" 300 | else: 301 | self.game = registry[stateDict['game']] 302 | 303 | self.charactors = [] 304 | for cID in stateDict['charactors']: 305 | if not registry.has_key( cID ): 306 | registry[cID] = Charactor( self.evManager ) 307 | neededObjIDs.append( cID ) 308 | success = False 309 | else: 310 | self.charactors.append( registry[cID] ) 311 | 312 | return [success, neededObjIDs] 313 | 314 | MixInClass( Player, CopyablePlayer ) 315 | 316 | #------------------------------------------------------------------------------ 317 | class CopyableCharactor(Serializable): 318 | copyworthy_attrs = ['sector', 'state'] 319 | 320 | def setCopyableState(self, stateDict, registry): 321 | neededObjIDs = [] 322 | success = True 323 | 324 | self.state = stateDict['state'] 325 | 326 | if stateDict['sector'] == None: 327 | self.sector = None 328 | elif not registry.has_key( stateDict['sector'] ): 329 | registry[stateDict['sector']] = Sector(self.evManager) 330 | neededObjIDs.append( stateDict['sector'] ) 331 | success = False 332 | else: 333 | self.sector = registry[stateDict['sector']] 334 | 335 | return [success, neededObjIDs] 336 | 337 | 338 | MixInClass( Charactor, CopyableCharactor ) 339 | 340 | #------------------------------------------------------------------------------ 341 | # Copyable Sector is not necessary in this simple example because the sectors 342 | # all get copied over in CopyableMap 343 | #------------------------------------------------------------------------------ 344 | #------------------------------------------------------------------------------ 345 | class CopyableSector: 346 | def getStateToCopy(self, registry): 347 | return {} 348 | #d = self.__dict__.copy() 349 | #del d['evManager'] 350 | #d['neighbors'][DIRECTION_UP] = id(d['neighbors'][DIRECTION_UP]) 351 | #d['neighbors'][DIRECTION_DOWN] = id(d['neighbors'][DIRECTION_DOWN]) 352 | #d['neighbors'][DIRECTION_LEFT] = id(d['neighbors'][DIRECTION_LEFT]) 353 | #d['neighbors'][DIRECTION_RIGHT] = id(d['neighbors'][DIRECTION_RIGHT]) 354 | #return d 355 | 356 | def setCopyableState(self, stateDict, registry): 357 | return [True, []] 358 | #neededObjIDs = [] 359 | #success = True 360 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_UP]): 361 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_UP] ) 362 | #success = 0 363 | #else: 364 | #self.neighbors[DIRECTION_UP] = registry[stateDict['neighbors'][DIRECTION_UP]] 365 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_DOWN]): 366 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_DOWN] ) 367 | #success = 0 368 | #else: 369 | #self.neighbors[DIRECTION_DOWN] = registry[stateDict['neighbors'][DIRECTION_DOWN]] 370 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_LEFT]): 371 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_LEFT] ) 372 | #success = 0 373 | #else: 374 | #self.neighbors[DIRECTION_LEFT] = registry[stateDict['neighbors'][DIRECTION_LEFT]] 375 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_RIGHT]): 376 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_RIGHT] ) 377 | #success = 0 378 | #else: 379 | #self.neighbors[DIRECTION_RIGHT] = registry[stateDict['neighbors'][DIRECTION_RIGHT]] 380 | 381 | #return [success, neededObjIDs] 382 | 383 | MixInClass( Sector, CopyableSector ) 384 | -------------------------------------------------------------------------------- /examples/network_and_menu/client.py: -------------------------------------------------------------------------------- 1 | import network 2 | import twisted.internet 3 | from twisted.spread import pb 4 | from twisted.internet.task import LoopingCall 5 | from twisted.internet.selectreactor import SelectReactor 6 | from twisted.internet.main import installReactor 7 | from events import * 8 | import example1 9 | from example1 import (EventManager, 10 | MenuKeyboardController, 11 | GameKeyboardController, 12 | PygameView) 13 | 14 | serverHost, serverPort = 'localhost', 8000 15 | 16 | #------------------------------------------------------------------------------ 17 | class CPUSpinnerController: 18 | def __init__(self, evManager): 19 | self.evManager = evManager 20 | self.evManager.RegisterListener( self ) 21 | 22 | self.keepGoing = 1 23 | self.replacementSpinner = None 24 | 25 | #---------------------------------------------------------------------- 26 | def Run(self): 27 | while self.keepGoing: 28 | event = TickEvent() 29 | self.evManager.Post( event ) 30 | print 'CPU spinner done' 31 | if self.replacementSpinner: 32 | print 'replacement spinner run()' 33 | self.replacementSpinner.Run() 34 | 35 | #---------------------------------------------------------------------- 36 | def SwitchToReactorSpinner(self): 37 | self.keepGoing = False 38 | self.replacementSpinner = ReactorSpinController(self.evManager) 39 | 40 | #---------------------------------------------------------------------- 41 | def Notify(self, event): 42 | if isinstance( event, QuitEvent ): 43 | self.keepGoing = False 44 | 45 | 46 | #------------------------------------------------------------------------------ 47 | class ReactorSpinController: 48 | STATE_STOPPED = 0 49 | STATE_STARTED = 1 50 | STATE_SHUTTING_DOWN = 2 51 | 52 | def __init__(self, evManager): 53 | self.state = ReactorSpinController.STATE_STOPPED 54 | self.evManager = evManager 55 | self.evManager.RegisterListener( self ) 56 | self.reactor = SelectReactor() 57 | installReactor(self.reactor) 58 | self.loopingCall = LoopingCall(self.FireTick) 59 | 60 | #---------------------------------------------------------------------- 61 | def FireTick(self): 62 | self.evManager.Post( TickEvent() ) 63 | 64 | #---------------------------------------------------------------------- 65 | def Run(self): 66 | self.state = ReactorSpinController.STATE_STARTED 67 | framesPerSecond = 10 68 | interval = 1.0 / framesPerSecond 69 | self.loopingCall.start(interval) 70 | self.reactor.run() 71 | 72 | #---------------------------------------------------------------------- 73 | def Stop(self): 74 | print 'stopping the reactor' 75 | self.state = ReactorSpinController.STATE_SHUTTING_DOWN 76 | self.reactor.addSystemEventTrigger('after', 'shutdown', 77 | self.onReactorStop) 78 | self.reactor.stop() 79 | 80 | #---------------------------------------------------------------------- 81 | def onReactorStop(self): 82 | print 'reactor is now totally stopped' 83 | self.state = ReactorSpinController.STATE_STOPPED 84 | self.reactor = None 85 | 86 | #---------------------------------------------------------------------- 87 | def Notify(self, event): 88 | if isinstance( event, QuitEvent ): 89 | self.Stop() 90 | 91 | 92 | #------------------------------------------------------------------------------ 93 | class NetworkServerView(pb.Root): 94 | """We SEND events to the server through this object""" 95 | STATE_PREPARING = 0 96 | STATE_CONNECTING = 1 97 | STATE_CONNECTED = 2 98 | STATE_DISCONNECTING = 3 99 | STATE_DISCONNECTED = 4 100 | 101 | #---------------------------------------------------------------------- 102 | def __init__(self, evManager, sharedObjectRegistry): 103 | self.evManager = evManager 104 | self.evManager.RegisterListener( self ) 105 | 106 | self.pbClientFactory = pb.PBClientFactory() 107 | self.state = NetworkServerView.STATE_PREPARING 108 | self.server = None 109 | self.connection = None 110 | 111 | self.sharedObjs = sharedObjectRegistry 112 | 113 | #---------------------------------------------------------------------- 114 | def AttemptConnection(self): 115 | print "attempting a connection to", serverHost, serverPort 116 | try: 117 | reactor = twisted.internet.reactor 118 | except AttributeError: 119 | print 'Reactor not yet installed!' 120 | return 121 | self.state = NetworkServerView.STATE_CONNECTING 122 | self.connection = reactor.connectTCP(serverHost, serverPort, 123 | self.pbClientFactory) 124 | deferred = self.pbClientFactory.getRootObject() 125 | deferred.addCallback(self.Connected) 126 | deferred.addErrback(self.ConnectFailed) 127 | 128 | #---------------------------------------------------------------------- 129 | def Disconnect(self): 130 | print 'disconnecting', self.connection 131 | self.connection.disconnect() 132 | self.state = NetworkServerView.STATE_DISCONNECTED 133 | 134 | #---------------------------------------------------------------------- 135 | def Connected(self, server): 136 | print "CONNECTED" 137 | self.server = server 138 | self.state = NetworkServerView.STATE_CONNECTED 139 | ev = ServerConnectEvent( server ) 140 | self.evManager.Post( ev ) 141 | 142 | #---------------------------------------------------------------------- 143 | def ConnectFailed(self, server): 144 | print "CONNECTION FAILED" 145 | self.state = NetworkServerView.STATE_DISCONNECTED 146 | self.evManager.Post(ConnectFail(server)) 147 | 148 | #---------------------------------------------------------------------- 149 | def Notify(self, event): 150 | if isinstance( event, RequestServerConnectEvent ): 151 | if self.state == NetworkServerView.STATE_PREPARING: 152 | self.AttemptConnection() 153 | return 154 | 155 | if isinstance( event, QuitEvent ): 156 | self.Disconnect() 157 | return 158 | 159 | ev = event 160 | if not isinstance( event, pb.Copyable ): 161 | evName = event.__class__.__name__ 162 | copyableClsName = "Copyable"+evName 163 | if not hasattr( network, copyableClsName ): 164 | return 165 | copyableClass = getattr( network, copyableClsName ) 166 | ev = copyableClass( event, self.sharedObjs ) 167 | 168 | if ev.__class__ not in network.clientToServerEvents: 169 | print "CLIENT NOT SENDING: " +str(ev) 170 | return 171 | 172 | if self.server: 173 | print " ==== Client sending", str(ev) 174 | remoteCall = self.server.callRemote("EventOverNetwork", 175 | ev) 176 | else: 177 | print " =--= Cannot send while disconnected:", str(ev) 178 | 179 | 180 | 181 | #------------------------------------------------------------------------------ 182 | class NetworkServerController(pb.Referenceable): 183 | """We RECEIVE events from the server through this object""" 184 | def __init__(self, evManager): 185 | self.server = None 186 | self.evManager = evManager 187 | self.evManager.RegisterListener( self ) 188 | 189 | #---------------------------------------------------------------------- 190 | def remote_ServerEvent(self, event): 191 | print " ==== GOT AN EVENT FROM SERVER:", str(event) 192 | self.evManager.Post( event ) 193 | return 1 194 | 195 | #---------------------------------------------------------------------- 196 | def OnSelfAddedToServer(self, *args): 197 | print 'success callback triggered' 198 | event = BothSidesConnectedEvent() 199 | self.evManager.Post( event ) 200 | 201 | #---------------------------------------------------------------------- 202 | def OnServerAddSelfFailed(self, *args): 203 | print 'fail callback triggered', args 204 | print dir(args[0]) 205 | print args[0].printDetailedTraceback() 206 | event = ConnectFail( self.server ) 207 | self.evManager.Post( event ) 208 | 209 | #---------------------------------------------------------------------- 210 | def Notify(self, event): 211 | if isinstance( event, ServerConnectEvent ): 212 | print 'connecting serv controller' 213 | #tell the server that we're listening to it and 214 | #it can access this object 215 | self.server = event.server 216 | d = self.server.callRemote("ClientConnect", self) 217 | d.addCallback(self.OnSelfAddedToServer) 218 | d.addErrback(self.OnServerAddSelfFailed) 219 | 220 | 221 | #------------------------------------------------------------------------------ 222 | class PhonyEventManager(EventManager): 223 | #---------------------------------------------------------------------- 224 | def Post( self, event ): 225 | pass 226 | 227 | #------------------------------------------------------------------------------ 228 | class PhonyModel(object): 229 | '''This isn't the authouritative model. That one exists on the 230 | server. This is a model to store local state and to interact with 231 | the local EventManager. 232 | ''' 233 | def __init__(self, evManager, sharedObjectRegistry, 234 | controller, spinner): 235 | self.sharedObjs = sharedObjectRegistry 236 | self.controller = controller 237 | self.spinner = spinner 238 | self.game = None 239 | self.server = None 240 | self.phonyEvManager = PhonyEventManager() 241 | self.realEvManager = evManager 242 | self.onConnectEvents = [] 243 | 244 | self.realEvManager.RegisterListener( self ) 245 | 246 | #---------------------------------------------------------------------- 247 | def StateReturned(self, response): 248 | if response[0] == 0: 249 | print "GOT ZERO -- better error handler here" 250 | return None 251 | objID = response[0] 252 | objDict = response[1] 253 | obj = self.sharedObjs[objID] 254 | 255 | retval = obj.setCopyableState(objDict, self.sharedObjs) 256 | if retval[0] == 1: 257 | return obj 258 | for remainingObjID in retval[1]: 259 | remoteResponse = self.server.callRemote("GetObjectState", remainingObjID) 260 | remoteResponse.addCallback(self.StateReturned) 261 | 262 | #TODO: look under the Twisted Docs for "Chaining Defferreds" 263 | retval = obj.setCopyableState(objDict, self.sharedObjs) 264 | if retval[0] == 0: 265 | print "WEIRD!!!!!!!!!!!!!!!!!!" 266 | return None 267 | 268 | return obj 269 | 270 | #---------------------------------------------------------------------- 271 | def Notify(self, event): 272 | if isinstance( event, ServerConnectEvent ): 273 | self.server = event.server 274 | elif isinstance( event, network.CopyableGameStartedEvent ): 275 | gameID = event.gameID 276 | if not self.game: 277 | # give a phony event manager to the local game 278 | # object so it won't be able to fire events 279 | self.game = example1.Game( self.phonyEvManager ) 280 | self.sharedObjs[gameID] = self.game 281 | print 'sending the gse to the real em.' 282 | ev = GameStartedEvent( self.game ) 283 | self.realEvManager.Post( ev ) 284 | 285 | if isinstance( event, network.CopyableMapBuiltEvent ): 286 | mapID = event.mapID 287 | if not self.game: 288 | self.game = example1.Game( self.phonyEvManager ) 289 | if self.sharedObjs.has_key(mapID): 290 | map = self.sharedObjs[mapID] 291 | ev = MapBuiltEvent( map ) 292 | self.realEvManager.Post( ev ) 293 | else: 294 | map = self.game.map 295 | self.sharedObjs[mapID] = map 296 | remoteResponse = self.server.callRemote("GetObjectState", mapID) 297 | remoteResponse.addCallback(self.StateReturned) 298 | remoteResponse.addCallback(self.MapBuiltCallback) 299 | 300 | if isinstance( event, network.CopyableCharactorPlaceEvent ): 301 | charactorID = event.charactorID 302 | if self.sharedObjs.has_key(charactorID): 303 | charactor = self.sharedObjs[charactorID] 304 | ev = CharactorPlaceEvent( charactor ) 305 | self.realEvManager.Post( ev ) 306 | else: 307 | charactor = self.game.players[0].charactors[0] 308 | self.sharedObjs[charactorID] = charactor 309 | remoteResponse = self.server.callRemote("GetObjectState", charactorID) 310 | remoteResponse.addCallback(self.StateReturned) 311 | remoteResponse.addCallback(self.CharactorPlaceCallback) 312 | 313 | if isinstance( event, network.CopyableCharactorMoveEvent ): 314 | charactorID = event.charactorID 315 | if self.sharedObjs.has_key(charactorID): 316 | charactor = self.sharedObjs[charactorID] 317 | else: 318 | charactor = self.game.players[0].charactors[0] 319 | self.sharedObjs[charactorID] = charactor 320 | remoteResponse = self.server.callRemote("GetObjectState", charactorID) 321 | remoteResponse.addCallback(self.StateReturned) 322 | remoteResponse.addCallback(self.CharactorMoveCallback) 323 | 324 | if isinstance( event, MenuMultiPlayerEvent ): 325 | self.StartMultiplayer() 326 | 327 | if isinstance( event, BothSidesConnectedEvent ): 328 | self.OnServerConnectSuccess() 329 | 330 | #---------------------------------------------------------------------- 331 | def CharactorPlaceCallback(self, charactor): 332 | ev = CharactorPlaceEvent( charactor ) 333 | self.realEvManager.Post( ev ) 334 | #---------------------------------------------------------------------- 335 | def MapBuiltCallback(self, map): 336 | ev = MapBuiltEvent( map ) 337 | self.realEvManager.Post( ev ) 338 | #---------------------------------------------------------------------- 339 | def CharactorMoveCallback(self, charactor): 340 | ev = CharactorMoveEvent( charactor ) 341 | self.realEvManager.Post( ev ) 342 | 343 | #---------------------------------------------------------------------- 344 | def OnServerConnectSuccess(self): 345 | # now that we're connected, post all the queued events 346 | while self.onConnectEvents: 347 | ev = self.onConnectEvents.pop(0) 348 | self.realEvManager.Post(ev) 349 | 350 | #---------------------------------------------------------------------- 351 | def OnServerConnectFail(self): 352 | self.onConnectEvents = [] 353 | 354 | #---------------------------------------------------------------------- 355 | def StartMultiplayer(self): 356 | self.spinner.SwitchToReactorSpinner() 357 | self.serverController = \ 358 | NetworkServerController(self.realEvManager) 359 | self.serverView = \ 360 | NetworkServerView(self.realEvManager, self.sharedObjs) 361 | self.controller = GameKeyboardController( self.realEvManager ) 362 | self.realEvManager.Post(RequestServerConnectEvent()) 363 | self.onConnectEvents.append(GameStartRequest()) 364 | 365 | 366 | #------------------------------------------------------------------------------ 367 | def main(): 368 | sharedObjectRegistry = {} 369 | evManager = EventManager() 370 | 371 | spinner = CPUSpinnerController( evManager ) 372 | pygameView = PygameView( evManager ) 373 | controller = MenuKeyboardController( evManager ) 374 | phonyModel = PhonyModel( evManager, sharedObjectRegistry, 375 | controller, spinner ) 376 | 377 | #from twisted.spread.jelly import globalSecurity 378 | #globalSecurity.allowModules( network ) 379 | 380 | spinner.Run() 381 | print 'Done Run' 382 | print evManager.eventQueue 383 | 384 | if __name__ == "__main__": 385 | main() 386 | -------------------------------------------------------------------------------- /examples/network_and_menu/events.py: -------------------------------------------------------------------------------- 1 | #SECURITY NOTE: anything in here can be created simply by sending the 2 | # class name over the network. This is a potential vulnerability 3 | # I wouldn't suggest letting any of these classes DO anything, especially 4 | # things like file system access, or allocating huge amounts of memory 5 | 6 | class Event: 7 | """this is a superclass for any events that might be generated by an 8 | object and sent to the EventManager""" 9 | def __init__(self): 10 | self.name = "Generic Event" 11 | 12 | class TickEvent(Event): 13 | def __init__(self): 14 | self.name = "CPU Tick Event" 15 | 16 | class QuitEvent(Event): 17 | def __init__(self): 18 | self.name = "Program Quit Event" 19 | 20 | class MapBuiltEvent(Event): 21 | def __init__(self, map): 22 | self.name = "Map Finished Building Event" 23 | self.map = map 24 | 25 | class GameStartRequest(Event): 26 | def __init__(self): 27 | self.name = "Game Start Request" 28 | 29 | class GameStartedEvent(Event): 30 | def __init__(self, game): 31 | self.name = "Game Started Event" 32 | self.game = game 33 | 34 | class CharactorMoveRequest(Event): 35 | def __init__(self, direction): 36 | self.name = "Charactor Move Request" 37 | self.direction = direction 38 | 39 | class CharactorMoveEvent(Event): 40 | def __init__(self, charactor): 41 | self.name = "Charactor Move Event" 42 | self.charactor = charactor 43 | 44 | class CharactorPlaceEvent(Event): 45 | """this event occurs when a Charactor is *placed* in a sector, 46 | ie it doesn't move there from an adjacent sector.""" 47 | def __init__(self, charactor): 48 | self.name = "Charactor Placement Event" 49 | self.charactor = charactor 50 | 51 | class ServerConnectEvent(Event): 52 | """the client generates this when it detects that it has successfully 53 | connected to the server""" 54 | def __init__(self, serverReference): 55 | self.name = "Network Server Connection Event" 56 | self.server = serverReference 57 | 58 | class ClientConnectEvent(Event): 59 | """this event is generated by the Server whenever a client connects 60 | to it""" 61 | def __init__(self, client): 62 | self.name = "Network Client Connection Event" 63 | self.client = client 64 | 65 | class MenuMultiPlayerEvent(Event): 66 | def __init__(self): 67 | self.name = "Multi Player Selected From Menu" 68 | 69 | class RequestServerConnectEvent(Event): 70 | def __init__(self): 71 | self.name = "Connect to Remote Server" 72 | 73 | class BothSidesConnectedEvent(Event): 74 | def __init__(self): 75 | self.name = "Controller and View Connected to Remote Server" 76 | 77 | class ConnectFail(Event): 78 | def __init__(self, host): 79 | self.name = "Controller or View Failed Connected to Host" 80 | self.host = host 81 | -------------------------------------------------------------------------------- /examples/network_and_menu/network.py: -------------------------------------------------------------------------------- 1 | 2 | from example1 import * 3 | from twisted.spread import pb 4 | 5 | # A list of ALL possible events that a server can send to a client 6 | serverToClientEvents = [] 7 | # A list of ALL possible events that a client can send to a server 8 | clientToServerEvents = [] 9 | 10 | #------------------------------------------------------------------------------ 11 | #Mix-In Helper Functions 12 | #------------------------------------------------------------------------------ 13 | def MixInClass( origClass, addClass ): 14 | if addClass not in origClass.__bases__: 15 | origClass.__bases__ += (addClass,) 16 | 17 | #------------------------------------------------------------------------------ 18 | def MixInCopyClasses( someClass ): 19 | MixInClass( someClass, pb.Copyable ) 20 | MixInClass( someClass, pb.RemoteCopy ) 21 | 22 | 23 | 24 | #------------------------------------------------------------------------------ 25 | class CopyableCharactor: 26 | def getStateToCopy(self): 27 | d = self.__dict__.copy() 28 | del d['evManager'] 29 | d['sector'] = id( self.sector ) 30 | return d 31 | def setCopyableState(self, stateDict, registry): 32 | neededObjIDs = [] 33 | success = 1 34 | if not registry.has_key( stateDict['sector'] ): 35 | neededObjIDs.append( stateDict['sector'] ) 36 | success = 0 37 | else: 38 | self.sector = registry[stateDict['sector']] 39 | return [success, neededObjIDs] 40 | 41 | 42 | MixInClass( Charactor, CopyableCharactor ) 43 | 44 | #------------------------------------------------------------------------------ 45 | # Copyable Sector is not necessary in this simple example because the sectors 46 | # all get copied over in CopyableMap 47 | #------------------------------------------------------------------------------ 48 | #------------------------------------------------------------------------------ 49 | #class CopyableSector: 50 | #def getStateToCopy(self): 51 | #d = self.__dict__.copy() 52 | #del d['evManager'] 53 | #d['neighbors'][DIRECTION_UP] = id(d['neighbors'][DIRECTION_UP]) 54 | #d['neighbors'][DIRECTION_DOWN] = id(d['neighbors'][DIRECTION_DOWN]) 55 | #d['neighbors'][DIRECTION_LEFT] = id(d['neighbors'][DIRECTION_LEFT]) 56 | #d['neighbors'][DIRECTION_RIGHT] = id(d['neighbors'][DIRECTION_RIGHT]) 57 | #return d 58 | # 59 | #def setCopyableState(self, stateDict, registry): 60 | #neededObjIDs = [] 61 | #success = 1 62 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_UP]): 63 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_UP] ) 64 | #success = 0 65 | #else: 66 | #self.neighbors[DIRECTION_UP] = registry[stateDict['neighbors'][DIRECTION_UP]] 67 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_DOWN]): 68 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_DOWN] ) 69 | #success = 0 70 | #else: 71 | #self.neighbors[DIRECTION_DOWN] = registry[stateDict['neighbors'][DIRECTION_DOWN]] 72 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_LEFT]): 73 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_LEFT] ) 74 | #success = 0 75 | #else: 76 | #self.neighbors[DIRECTION_LEFT] = registry[stateDict['neighbors'][DIRECTION_LEFT]] 77 | #if not registry.has_key( stateDict['neighbors'][DIRECTION_RIGHT]): 78 | #neededObjIDs.append( stateDict['neighbors'][DIRECTION_RIGHT] ) 79 | #success = 0 80 | #else: 81 | #self.neighbors[DIRECTION_RIGHT] = registry[stateDict['neighbors'][DIRECTION_RIGHT]] 82 | # 83 | #return [success, neededObjIDs] 84 | # 85 | #MixInClass( Sector, CopyableSector ) 86 | 87 | #------------------------------------------------------------------------------ 88 | class CopyableMap: 89 | def getStateToCopy(self): 90 | sectorIDList = [] 91 | for sect in self.sectors: 92 | sectorIDList.append( id(sect) ) 93 | return {'ninegrid':1, 'sectorIDList':sectorIDList} 94 | 95 | 96 | def setCopyableState(self, stateDict, registry): 97 | neededObjIDs = [] 98 | success = 1 99 | 100 | if self.state != Map.STATE_BUILT: 101 | self.Build() 102 | 103 | i = 0 104 | for sectID in stateDict['sectorIDList']: 105 | registry[sectID] = self.sectors[i] 106 | i += 1 107 | 108 | return [success, neededObjIDs] 109 | 110 | MixInClass( Map, CopyableMap ) 111 | 112 | 113 | 114 | #------------------------------------------------------------------------------ 115 | #------------------------------------------------------------------------------ 116 | # For each event class, if it is sendable over the network, we have 117 | # to Mix In the "copy classes", or make a replacement event class that is 118 | # copyable 119 | 120 | #------------------------------------------------------------------------------ 121 | # TickEvent 122 | # Direction: don't send. 123 | #The Tick event happens hundreds of times per second. If we think we need 124 | #to send it over the network, we should REALLY re-evaluate our design 125 | 126 | #------------------------------------------------------------------------------ 127 | # QuitEvent 128 | # Direction: Client to Server only 129 | MixInCopyClasses( QuitEvent ) 130 | pb.setUnjellyableForClass(QuitEvent, QuitEvent) 131 | clientToServerEvents.append( QuitEvent ) 132 | 133 | #------------------------------------------------------------------------------ 134 | # GameStartRequest 135 | # Direction: Client to Server only 136 | MixInCopyClasses( GameStartRequest ) 137 | pb.setUnjellyableForClass(GameStartRequest, GameStartRequest) 138 | clientToServerEvents.append( GameStartRequest ) 139 | 140 | #------------------------------------------------------------------------------ 141 | # CharactorMoveRequest 142 | # Direction: Client to Server only 143 | # this has an additional attribute, direction. it is an int, so it's safe 144 | MixInCopyClasses( CharactorMoveRequest ) 145 | pb.setUnjellyableForClass(CharactorMoveRequest, CharactorMoveRequest) 146 | clientToServerEvents.append( CharactorMoveRequest ) 147 | 148 | 149 | #------------------------------------------------------------------------------ 150 | # ServerConnectEvent 151 | # Direction: don't send. 152 | # we don't need to send this over the network. 153 | 154 | #------------------------------------------------------------------------------ 155 | # ClientConnectEvent 156 | # Direction: don't send. 157 | # we don't need to send this over the network. 158 | 159 | 160 | #------------------------------------------------------------------------------ 161 | # GameStartedEvent 162 | # Direction: Server to Client only 163 | class CopyableGameStartedEvent(pb.Copyable, pb.RemoteCopy): 164 | def __init__(self, event, registry): 165 | #self.game = netwrap.WrapInstance(event.game) 166 | self.name = "Copyable Game Started Event" 167 | self.gameID = id(event.game) 168 | registry[self.gameID] = event.game 169 | 170 | pb.setUnjellyableForClass(CopyableGameStartedEvent, CopyableGameStartedEvent) 171 | serverToClientEvents.append( CopyableGameStartedEvent ) 172 | 173 | #------------------------------------------------------------------------------ 174 | # MapBuiltEvent 175 | # Direction: Server to Client only 176 | class CopyableMapBuiltEvent( pb.Copyable, pb.RemoteCopy): 177 | def __init__(self, event, registry ): 178 | self.name = "Copyable Map Finished Building Event" 179 | self.mapID = id( event.map ) 180 | registry[self.mapID] = event.map 181 | 182 | pb.setUnjellyableForClass(CopyableMapBuiltEvent, CopyableMapBuiltEvent) 183 | serverToClientEvents.append( CopyableMapBuiltEvent ) 184 | 185 | #------------------------------------------------------------------------------ 186 | # CharactorMoveEvent 187 | # Direction: Server to Client only 188 | class CopyableCharactorMoveEvent( pb.Copyable, pb.RemoteCopy): 189 | def __init__(self, event, registry ): 190 | self.name = "Copyable Charactor Move Event" 191 | self.charactorID = id( event.charactor ) 192 | registry[self.charactorID] = event.charactor 193 | 194 | pb.setUnjellyableForClass(CopyableCharactorMoveEvent, CopyableCharactorMoveEvent) 195 | serverToClientEvents.append( CopyableCharactorMoveEvent ) 196 | 197 | #------------------------------------------------------------------------------ 198 | # CharactorPlaceEvent 199 | # Direction: Server to Client only 200 | class CopyableCharactorPlaceEvent( pb.Copyable, pb.RemoteCopy): 201 | def __init__(self, event, registry ): 202 | self.name = "Copyable Charactor Placement Event" 203 | self.charactorID = id( event.charactor ) 204 | registry[self.charactorID] = event.charactor 205 | 206 | pb.setUnjellyableForClass(CopyableCharactorPlaceEvent, CopyableCharactorPlaceEvent) 207 | serverToClientEvents.append( CopyableCharactorPlaceEvent ) 208 | 209 | 210 | -------------------------------------------------------------------------------- /examples/network_and_menu/server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ''' 3 | Example server 4 | ''' 5 | 6 | from twisted.spread import pb 7 | from example1 import EventManager, Game 8 | from events import * 9 | import network 10 | 11 | #------------------------------------------------------------------------------ 12 | class NoTickEventManager(EventManager): 13 | '''This subclass of EventManager doesn't wait for a Tick event before 14 | it starts consuming its event queue. The server module doesn't have 15 | a CPUSpinnerController, so Ticks will not get generated. 16 | ''' 17 | def __init__(self): 18 | EventManager.__init__(self) 19 | self._lock = False 20 | def Post(self, event): 21 | EventManager.Post(self,event) 22 | if not self._lock: 23 | self._lock = True 24 | self.ConsumeEventQueue() 25 | self._lock = False 26 | 27 | 28 | 29 | #------------------------------------------------------------------------------ 30 | class NetworkClientController(pb.Root): 31 | """We RECEIVE events from the CLIENT through this object""" 32 | def __init__(self, evManager, sharedObjectRegistry): 33 | self.evManager = evManager 34 | self.evManager.RegisterListener( self ) 35 | self.sharedObjs = sharedObjectRegistry 36 | 37 | #---------------------------------------------------------------------- 38 | def remote_ClientConnect(self, netClient): 39 | print "CLIENT CONNECT" 40 | ev = ClientConnectEvent( netClient ) 41 | self.evManager.Post( ev ) 42 | 43 | #---------------------------------------------------------------------- 44 | def remote_GetObjectState(self, objectID): 45 | print "request for object state", objectID 46 | if not self.sharedObjs.has_key( objectID ): 47 | return [0,0] 48 | objDict = self.sharedObjs[objectID].getStateToCopy() 49 | return [objectID, objDict] 50 | 51 | #---------------------------------------------------------------------- 52 | def remote_EventOverNetwork(self, event): 53 | print "Server just got an EVENT" + str(event) 54 | self.evManager.Post( event ) 55 | return 1 56 | 57 | #---------------------------------------------------------------------- 58 | def Notify(self, event): 59 | pass 60 | 61 | 62 | #------------------------------------------------------------------------------ 63 | class NetworkClientView(object): 64 | """We SEND events to the CLIENT through this object""" 65 | def __init__(self, evManager, sharedObjectRegistry): 66 | self.evManager = evManager 67 | self.evManager.RegisterListener( self ) 68 | 69 | self.clients = [] 70 | self.sharedObjs = sharedObjectRegistry 71 | 72 | 73 | #---------------------------------------------------------------------- 74 | def Notify(self, event): 75 | if isinstance( event, ClientConnectEvent ): 76 | print '== adding a client', event.client 77 | self.clients.append( event.client ) 78 | 79 | ev = event 80 | 81 | #don't broadcast events that aren't Copyable 82 | if not isinstance( ev, pb.Copyable ): 83 | evName = ev.__class__.__name__ 84 | copyableClsName = "Copyable"+evName 85 | if not hasattr( network, copyableClsName ): 86 | return 87 | copyableClass = getattr( network, copyableClsName ) 88 | ev = copyableClass( ev, self.sharedObjs ) 89 | 90 | if ev.__class__ not in network.serverToClientEvents: 91 | print "SERVER NOT SENDING: " +str(ev) 92 | return 93 | 94 | #NOTE: this is very "chatty". We could restrict 95 | # the number of clients notified in the future 96 | for client in self.clients: 97 | print "=====server sending: ", str(ev) 98 | remoteCall = client.callRemote("ServerEvent", ev) 99 | 100 | 101 | #------------------------------------------------------------------------------ 102 | class TextLogView(object): 103 | def __init__(self, evManager): 104 | self.evManager = evManager 105 | self.evManager.RegisterListener( self ) 106 | 107 | #---------------------------------------------------------------------- 108 | def Notify(self, event): 109 | if isinstance( event, TickEvent ): 110 | return 111 | 112 | print 'TEXTLOG <', 113 | 114 | if isinstance( event, CharactorPlaceEvent ): 115 | print event.name, " at ", event.charactor.sector 116 | 117 | elif isinstance( event, CharactorMoveEvent ): 118 | print event.name, " to ", event.charactor.sector 119 | 120 | 121 | 122 | #------------------------------------------------------------------------------ 123 | def main(): 124 | evManager = NoTickEventManager() 125 | sharedObjectRegistry = {} 126 | 127 | log = TextLogView( evManager ) 128 | clientController = NetworkClientController( evManager, sharedObjectRegistry ) 129 | clientView = NetworkClientView( evManager, sharedObjectRegistry ) 130 | game = Game( evManager ) 131 | 132 | from twisted.internet import reactor 133 | reactor.listenTCP( 8000, pb.PBServerFactory(clientController) ) 134 | 135 | reactor.run() 136 | 137 | if __name__ == "__main__": 138 | main() 139 | -------------------------------------------------------------------------------- /examples/server1.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ''' 3 | Example server 4 | ''' 5 | 6 | from twisted.spread import pb 7 | 8 | def Debug( msg ): 9 | return 10 | #print msg 11 | 12 | DIRECTION_UP = 0 13 | DIRECTION_RIGHT = 1 14 | DIRECTION_LEFT = 2 15 | DIRECTION_DOWN = 3 16 | 17 | class Event: 18 | """this is a superclass for any events that might be generated by an 19 | object and sent to the EventManager""" 20 | def __init__(self): 21 | self.name = "Generic Event" 22 | 23 | class TickEvent(Event): 24 | def __init__(self): 25 | self.name = "CPU Tick Event" 26 | 27 | class QuitEvent(Event): 28 | def __init__(self): 29 | self.name = "Program Quit Event" 30 | 31 | class MapBuiltEvent(Event): 32 | def __init__(self, gameMap): 33 | self.name = "Map Finished Building Event" 34 | self.map = gameMap 35 | 36 | class GameStartRequest(Event): 37 | def __init__(self): 38 | self.name = "Game Start Request" 39 | 40 | class GameStartedEvent(Event): 41 | def __init__(self, game): 42 | self.name = "Game Started Event" 43 | self.game = game 44 | 45 | class CharactorMoveRequest(Event): 46 | def __init__(self, direction): 47 | self.name = "Charactor Move Request" 48 | self.direction = direction 49 | 50 | class CharactorPlaceEvent(Event): 51 | """this event occurs when a Charactor is *placed* in a sector, 52 | ie it doesn't move there from an adjacent sector.""" 53 | def __init__(self, charactor): 54 | self.name = "Charactor Placement Event" 55 | self.charactor = charactor 56 | 57 | class CharactorMoveEvent(Event): 58 | def __init__(self, charactor): 59 | self.name = "Charactor Move Event" 60 | self.charactor = charactor 61 | 62 | #------------------------------------------------------------------------------ 63 | class EventManager: 64 | """this object is responsible for coordinating most communication 65 | between the Model, View, and Controller.""" 66 | def __init__(self ): 67 | from weakref import WeakKeyDictionary 68 | self.listeners = WeakKeyDictionary() 69 | self.eventQueue= [] 70 | 71 | #---------------------------------------------------------------------- 72 | def RegisterListener( self, listener ): 73 | self.listeners[ listener ] = 1 74 | 75 | #---------------------------------------------------------------------- 76 | def UnregisterListener( self, listener ): 77 | if listener in self.listeners: 78 | del self.listeners[ listener ] 79 | 80 | #---------------------------------------------------------------------- 81 | def Notify( self, event ): 82 | if not isinstance(event, TickEvent): 83 | Debug( " Message: " + event.name ) 84 | for listener in self.listeners: 85 | #NOTE: If the weakref has died, it will be 86 | #automatically removed, so we don't have 87 | #to worry about it. 88 | listener.Notify( event ) 89 | 90 | 91 | #------------------------------------------------------------------------------ 92 | class NetworkClientController(pb.Root): 93 | """We RECEIVE events from the CLIENT through this object""" 94 | def __init__(self, evManager): 95 | self.evManager = evManager 96 | self.evManager.RegisterListener( self ) 97 | 98 | #---------------------------------------------------------------------- 99 | def remote_GameStartRequest(self): 100 | ev = GameStartRequest( ) 101 | self.evManager.Notify( ev ) 102 | return 1 103 | 104 | #---------------------------------------------------------------------- 105 | def remote_CharactorMoveRequest(self, direction): 106 | ev = CharactorMoveRequest( direction ) 107 | self.evManager.Notify( ev ) 108 | return 1 109 | 110 | #---------------------------------------------------------------------- 111 | def Notify(self, event): 112 | pass 113 | 114 | 115 | #------------------------------------------------------------------------------ 116 | class TextLogView: 117 | """...""" 118 | def __init__(self, evManager): 119 | self.evManager = evManager 120 | self.evManager.RegisterListener( self ) 121 | 122 | 123 | #---------------------------------------------------------------------- 124 | def Notify(self, event): 125 | 126 | if isinstance( event, CharactorPlaceEvent ): 127 | print event.name, " at ", event.charactor.sector 128 | 129 | elif isinstance( event, CharactorMoveEvent ): 130 | print event.name, " to ", event.charactor.sector 131 | 132 | elif not isinstance( event, TickEvent ): 133 | print event.name 134 | 135 | #------------------------------------------------------------------------------ 136 | class Game: 137 | """...""" 138 | 139 | STATE_PREPARING = 0 140 | STATE_RUNNING = 1 141 | STATE_PAUSED = 2 142 | 143 | #---------------------------------------------------------------------- 144 | def __init__(self, evManager): 145 | self.evManager = evManager 146 | self.evManager.RegisterListener( self ) 147 | 148 | self.state = Game.STATE_PREPARING 149 | 150 | self.players = [ Player(evManager) ] 151 | self.map = Map( evManager ) 152 | 153 | #---------------------------------------------------------------------- 154 | def Start(self): 155 | self.map.Build() 156 | self.state = Game.STATE_RUNNING 157 | ev = GameStartedEvent( self ) 158 | self.evManager.Notify( ev ) 159 | 160 | #---------------------------------------------------------------------- 161 | def Notify(self, event): 162 | if isinstance( event, GameStartRequest ): 163 | if self.state == Game.STATE_PREPARING: 164 | self.Start() 165 | 166 | #------------------------------------------------------------------------------ 167 | class Player: 168 | """...""" 169 | def __init__(self, evManager): 170 | self.evManager = evManager 171 | #self.evManager.RegisterListener( self ) 172 | 173 | self.charactors = [ Charactor(evManager) ] 174 | 175 | #------------------------------------------------------------------------------ 176 | class Charactor: 177 | """...""" 178 | 179 | STATE_INACTIVE = 0 180 | STATE_ACTIVE = 1 181 | 182 | def __init__(self, evManager): 183 | self.evManager = evManager 184 | self.evManager.RegisterListener( self ) 185 | self.sector = None 186 | self.state = Charactor.STATE_INACTIVE 187 | 188 | #---------------------------------------------------------------------- 189 | def Move(self, direction): 190 | if self.state == Charactor.STATE_INACTIVE: 191 | return 192 | 193 | if self.sector.MovePossible( direction ): 194 | newSector = self.sector.neighbors[direction] 195 | self.sector = newSector 196 | ev = CharactorMoveEvent( self ) 197 | self.evManager.Notify( ev ) 198 | 199 | #---------------------------------------------------------------------- 200 | def Place(self, sector): 201 | self.sector = sector 202 | ev = CharactorPlaceEvent( self ) 203 | self.evManager.Notify( ev ) 204 | 205 | #---------------------------------------------------------------------- 206 | def Notify(self, event): 207 | if isinstance( event, GameStartedEvent ): 208 | gameMap = event.game.map 209 | self.Place( gameMap.sectors[gameMap.startSectorIndex] ) 210 | self.state = Charactor.STATE_ACTIVE 211 | 212 | elif isinstance( event, CharactorMoveRequest ): 213 | self.Move( event.direction ) 214 | 215 | #------------------------------------------------------------------------------ 216 | class Map: 217 | """...""" 218 | def __init__(self, evManager): 219 | self.evManager = evManager 220 | #self.evManager.RegisterListener( self ) 221 | 222 | self.sectors = range(9) 223 | self.startSectorIndex = 0 224 | 225 | #---------------------------------------------------------------------- 226 | def Build(self): 227 | for i in range(9): 228 | self.sectors[i] = Sector( self.evManager ) 229 | 230 | self.sectors[3].neighbors[DIRECTION_UP] = self.sectors[0] 231 | self.sectors[4].neighbors[DIRECTION_UP] = self.sectors[1] 232 | self.sectors[5].neighbors[DIRECTION_UP] = self.sectors[2] 233 | self.sectors[6].neighbors[DIRECTION_UP] = self.sectors[3] 234 | self.sectors[7].neighbors[DIRECTION_UP] = self.sectors[4] 235 | self.sectors[8].neighbors[DIRECTION_UP] = self.sectors[5] 236 | 237 | self.sectors[0].neighbors[DIRECTION_RIGHT] = self.sectors[1] 238 | self.sectors[1].neighbors[DIRECTION_RIGHT] = self.sectors[2] 239 | self.sectors[3].neighbors[DIRECTION_RIGHT] = self.sectors[4] 240 | self.sectors[4].neighbors[DIRECTION_RIGHT] = self.sectors[5] 241 | self.sectors[6].neighbors[DIRECTION_RIGHT] = self.sectors[7] 242 | self.sectors[7].neighbors[DIRECTION_RIGHT] = self.sectors[8] 243 | 244 | self.sectors[0].neighbors[DIRECTION_DOWN] = self.sectors[3] 245 | self.sectors[1].neighbors[DIRECTION_DOWN] = self.sectors[4] 246 | self.sectors[2].neighbors[DIRECTION_DOWN] = self.sectors[5] 247 | self.sectors[3].neighbors[DIRECTION_DOWN] = self.sectors[6] 248 | self.sectors[4].neighbors[DIRECTION_DOWN] = self.sectors[7] 249 | self.sectors[5].neighbors[DIRECTION_DOWN] = self.sectors[8] 250 | 251 | self.sectors[1].neighbors[DIRECTION_LEFT] = self.sectors[0] 252 | self.sectors[2].neighbors[DIRECTION_LEFT] = self.sectors[1] 253 | self.sectors[4].neighbors[DIRECTION_LEFT] = self.sectors[3] 254 | self.sectors[5].neighbors[DIRECTION_LEFT] = self.sectors[4] 255 | self.sectors[7].neighbors[DIRECTION_LEFT] = self.sectors[6] 256 | self.sectors[8].neighbors[DIRECTION_LEFT] = self.sectors[7] 257 | 258 | ev = MapBuiltEvent( self ) 259 | self.evManager.Notify( ev ) 260 | 261 | #------------------------------------------------------------------------------ 262 | class Sector: 263 | """...""" 264 | def __init__(self, evManager): 265 | self.evManager = evManager 266 | #self.evManager.RegisterListener( self ) 267 | 268 | self.neighbors = range(4) 269 | 270 | self.neighbors[DIRECTION_UP] = None 271 | self.neighbors[DIRECTION_RIGHT] = None 272 | self.neighbors[DIRECTION_DOWN] = None 273 | self.neighbors[DIRECTION_LEFT] = None 274 | 275 | #---------------------------------------------------------------------- 276 | def MovePossible(self, direction): 277 | if self.neighbors[direction]: 278 | return 1 279 | 280 | 281 | 282 | #------------------------------------------------------------------------------ 283 | def main(): 284 | """...""" 285 | evManager = EventManager() 286 | 287 | log = TextLogView( evManager ) 288 | clientController = NetworkClientController( evManager ) 289 | game = Game( evManager ) 290 | 291 | from twisted.internet import reactor 292 | 293 | reactor.listenTCP( 8000, pb.PBServerFactory(clientController) ) 294 | 295 | reactor.run() 296 | 297 | if __name__ == "__main__": 298 | main() 299 | -------------------------------------------------------------------------------- /fodt_tail.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /foolbar.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/foolbar.tar.gz -------------------------------------------------------------------------------- /game-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/game-model.png -------------------------------------------------------------------------------- /host_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/host_structure.png -------------------------------------------------------------------------------- /interactive_snippet.txt: -------------------------------------------------------------------------------- 1 | $ python 2 | Python 2.5.2 (r252:60911, Apr 21 2008, 11:17:30) 3 | [GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2 4 | Type "help", "copyright", "credits" or "license" for more information. 5 | >>> from twisted.spread import pb 6 | >>> from twisted.internet import reactor 7 | >>> factory = pb.PBClientFactory() 8 | >>> server = None 9 | >>> def gotServer(serv): 10 | ...     global server 11 | ...     server = serv 12 | ... 13 | >>> connection = reactor.connectTCP('localhost', 8000, factory) 14 | >>> reactor.callLater( 4, reactor.crash ) 15 | <twisted.internet.base.DelayedCall instance at 0xac5638> 16 | >>> reactor.run() 17 | >>> d = factory.getRootObject() 18 | >>> d.addCallback(gotServer) 19 | <Deferred at 0xb1f440  current result: None> 20 | >>> reactor.iterate() 21 | >>> server.callRemote('GameStartRequest') 22 | <Deferred at 0xac5638> 23 | >>> reactor.iterate() 24 | >>> up, right, down, left = 0,1,2,3 25 | >>> server.callRemote('CharactorMoveRequest', up) 26 | <Deferred at 0xb1f4d0> 27 | >>> reactor.iterate() 28 | >>> server.callRemote('CharactorMoveRequest', right) 29 | <Deferred at 0xac5638> 30 | >>> reactor.iterate() 31 | >>> server.callRemote('CharactorMoveRequest', down) 32 | <Deferred at 0xb1f4d0> 33 | >>> reactor.iterate() 34 | >>> server.callRemote('CharactorMoveRequest', left) 35 | <Deferred at 0xac5638> 36 | >>> reactor.iterate() 37 | 38 | -------------------------------------------------------------------------------- /keybd_monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/keybd_monitor.png -------------------------------------------------------------------------------- /make.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | s = os.system 4 | 5 | def create_table_of_contents(): 6 | s('egrep " /tmp/table.html') 7 | s('''sed -i 's/name=./href="writing-games.html#/g' /tmp/table.html''') 8 | 9 | fp = file('table.html', 'w') 10 | fp.write('''\ 11 | 12 | 13 | 26 | 27 | 28 | ''') 29 | fp.close() 30 | s('cat /tmp/table.html >> ./table.html') 31 | 32 | 33 | def create_targz_from_example(name): 34 | targen = ('git checkout %(name)s; ' 35 | 'cp -a code_examples %(name)s; ' 36 | 'tar -cv --exclude-vcs %(name)s > %(name)s.tar; ' 37 | 'rm -rf /tmp/%(name)s; ' 38 | 'mv %(name)s /tmp/%(name)s; ' 39 | 'gzip %(name)s.tar; ' 40 | ) 41 | s(targen % {'name': name}) 42 | 43 | create_table_of_contents() 44 | create_targz_from_example('example2') 45 | create_targz_from_example('example3') 46 | create_targz_from_example('example4') 47 | 48 | print 'Setting git branch to *master*' 49 | s('git checkout master') 50 | 51 | # always append a '/' on the src directory when rsyncing 52 | s('rsync -r ./ $DREAMHOST_USERNAME@ezide.com:/home/$DREAMHOST_USERNAME/ezide.com/games') 53 | -------------------------------------------------------------------------------- /make_chapter.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import re 3 | import cgi 4 | 5 | def parse(c): 6 | start_code = '----' 7 | end_code = '----' 8 | 9 | prompt_aside = '[ASIDE]' 10 | start_aside = '[' 11 | end_aside = ']' 12 | 13 | pState = 'out' 14 | codeState = 'out' 15 | asideState = 'out' 16 | listState = 'out' 17 | promptState = None 18 | 19 | lines = c.splitlines() 20 | 21 | sections = [] 22 | currentPara = '' 23 | currentCode = '' 24 | currentAside = '' 25 | currentList = '' 26 | 27 | for line in lines: 28 | sline = line.strip() 29 | if pState == 'in': 30 | if sline in ['', start_code, start_aside, prompt_aside]: 31 | pState = 'out' 32 | if currentPara: 33 | sections.append(('p', currentPara)) 34 | else: 35 | currentPara += ' ' + sline 36 | else: 37 | if (not (codeState == 'in' 38 | or asideState == 'in' 39 | or listState == 'in') 40 | and sline not in ['', start_code, start_aside, prompt_aside]): 41 | pState = 'in' 42 | currentPara = sline 43 | 44 | if codeState == 'in': 45 | if sline == end_code: 46 | codeState = 'out' 47 | if currentCode: 48 | sections.append(('code', currentCode)) 49 | else: 50 | currentCode += line + '\n' 51 | else: 52 | if sline == start_code: 53 | codeState = 'in' 54 | currentCode = '' 55 | 56 | if asideState == 'in': 57 | if sline == end_aside: 58 | asideState = 'out' 59 | if currentAside: 60 | aside = parse(currentAside) 61 | sections.append(('aside', aside)) 62 | else: 63 | currentAside += line + '\n' 64 | else: 65 | if sline == start_aside: 66 | asideState = 'in' 67 | currentAside = '' 68 | 69 | # clean up at the EOF 70 | if pState == 'in': 71 | if currentPara: 72 | sections.append(('p', currentPara)) 73 | 74 | if codeState == 'in': 75 | if currentCode: 76 | sections.append(('code', currentCode)) 77 | 78 | if asideState == 'in': 79 | if currentAside: 80 | aside = parse(currentAside) 81 | sections.append(('aside', aside)) 82 | 83 | return sections 84 | 85 | def render_dumb_html(sections): 86 | for sect in sections: 87 | if sect[0] == 'p': 88 | print '

' 89 | print sect[1] 90 | print '

' 91 | elif sect[0] == 'code': 92 | print '
'
 93 |             print sect[1]
 94 |             print '
' 95 | elif sect[0] == 'aside': 96 | print '
' 97 | render_dumb_html(sect[1]) 98 | print '
' 99 | 100 | def render_fodt(sections): 101 | fp = file('fodt_head.txt') 102 | c = fp.read() 103 | fp.close() 104 | write = sys.stdout.write 105 | write(c + '\n') 106 | def highlight_author_notes(text): 107 | s = '' 108 | e = '' 109 | start_todo = text.find('[TODO') 110 | if start_todo == -1: 111 | return text 112 | end_todo = text.find(']', start_todo) 113 | text = (text[:start_todo] + s + 114 | text[start_todo:end_todo+1] 115 | + e + text[end_todo+1:]) 116 | return text 117 | 118 | def render_sections(sections, paraStyle="Body"): 119 | for sect in sections: 120 | if sect[0] == 'p': 121 | body = sect[1] 122 | body = cgi.escape(body) 123 | body = highlight_author_notes(body) 124 | write('' % paraStyle) 125 | write(body) 126 | write('\n') 127 | elif sect[0] == 'code': 128 | body = sect[1] 129 | body = cgi.escape(body) 130 | for line in body.splitlines(): 131 | result = re.split('\S', line, 1) 132 | if len(result) > 1: 133 | spaces = result[0] 134 | else: 135 | spaces = '' 136 | write('') 137 | write('' % len(spaces)) 138 | write(line[len(spaces):]) 139 | write('\n') 140 | elif sect[0] == 'aside': 141 | #write('') 142 | write('\n') 143 | render_sections(sect[1], 'Note') 144 | write('\n') 145 | #write('\n') 146 | render_sections(sections) 147 | fp = file('fodt_tail.txt') 148 | c = fp.read() 149 | fp.close() 150 | write(c + '\n') 151 | 152 | 153 | def main(): 154 | chfile = sys.argv[1] 155 | chfile = file(chfile) 156 | chapter = chfile.read() 157 | chfile.close() 158 | 159 | sections = parse(chapter) 160 | #render_dumb_html(sections) 161 | render_fodt(sections) 162 | 163 | if __name__ == '__main__': 164 | main() 165 | -------------------------------------------------------------------------------- /mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/mockup.png -------------------------------------------------------------------------------- /multi_controller_test.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from twisted.internet.selectreactor import SelectReactor 3 | from twisted.spread import pb 4 | from twisted.internet.main import installReactor 5 | import pygame_test 6 | import time 7 | 8 | FRAMES_PER_SECOND = 4 9 | 10 | class ReactorController(SelectReactor): 11 | def __init__(self): 12 | SelectReactor.__init__(self) 13 | connection = self.connectTCP('localhost', 8000, factory) 14 | pygame_test.prepare() 15 | installReactor(self) 16 | 17 | def doIteration(self, delay): 18 | print 'calling doIteration' 19 | SelectReactor.doIteration(self,delay) 20 | retval = pygame_test.iterate() 21 | if retval == False: 22 | thingInControl.stop() 23 | 24 | 25 | 26 | class ReactorSlaveController(object): 27 | def __init__(self): 28 | self.keepGoing = True 29 | self.reactor = SelectReactor() 30 | installReactor(self.reactor) 31 | connection = self.reactor.connectTCP('localhost', 8000, factory) 32 | self.reactor.startRunning() 33 | self.futureCall = None 34 | self.futureCallTimeout = None 35 | pygame_test.prepare() 36 | 37 | def iterate(self): 38 | print 'in iterate' 39 | self.reactor.runUntilCurrent() 40 | self.reactor.doIteration(0) 41 | #t2 = self.reactor.timeout() 42 | #print 'timeout', t2 43 | #t = self.reactor.running and t2 44 | #self.reactor.doIteration(t) 45 | 46 | def run(self): 47 | clock = pygame.time.Clock() 48 | self.reactor.callLater(20, stupidTest) 49 | while self.keepGoing: 50 | timeChange = clock.tick(FRAMES_PER_SECOND) 51 | if self.futureCall: 52 | self.futureCallTimeout -= timeChange 53 | print 'future call in', self.futureCallTimeout 54 | if self.futureCallTimeout <= 0: 55 | self.futureCall() 56 | self.futureCallTimeout = None 57 | self.futureCall= None 58 | retval = pygame_test.iterate() 59 | if retval == False: 60 | thingInControl.stop() 61 | self.iterate() 62 | 63 | def stop(self): 64 | print 'stopping' 65 | self.reactor.stop() 66 | self.keepGoing = False 67 | 68 | def callLater(self, when, fn): 69 | self.futureCallTimeout = when*1000 70 | self.futureCall = fn 71 | print 'future call in', self.futureCallTimeout 72 | 73 | 74 | class LoopingCallController(object): 75 | def __init__(self): 76 | from twisted.internet import reactor 77 | from twisted.internet.task import LoopingCall 78 | self.reactor = reactor 79 | connection = self.reactor.connectTCP('localhost', 8000, factory) 80 | self.loopingCall = LoopingCall(self.iterate) 81 | pygame_test.prepare() 82 | 83 | def iterate(self): 84 | print 'looping call controller in iterate' 85 | retval = pygame_test.iterate() 86 | if retval == False: 87 | thingInControl.stop() 88 | 89 | def run(self): 90 | interval = 1.0 / FRAMES_PER_SECOND 91 | self.loopingCall.start(interval) 92 | self.reactor.run() 93 | 94 | def stop(self): 95 | self.reactor.stop() 96 | 97 | def callLater(self, when, fn): 98 | self.reactor.callLater(when, fn) 99 | 100 | def stupidTest(): 101 | print 'stupid test!' 102 | 103 | server = None 104 | def gotServer(serv): 105 | print '-'*79 106 | print 'got server', serv 107 | global server 108 | server = serv 109 | # stop in exactly 5 seconds 110 | thingInControl.callLater(65.0, stopLoop) 111 | 112 | def stopLoop(): 113 | print '-'*79 114 | print 'stopping the loop' 115 | thingInControl.stop() 116 | 117 | factory = pb.PBClientFactory() 118 | d = factory.getRootObject() 119 | d.addCallback(gotServer) 120 | 121 | import sys 122 | if len(sys.argv) < 2: 123 | print 'usage: test.py 1|2|3' 124 | sys.exit(1) 125 | elif sys.argv[1] == '1': 126 | thingInControl = ReactorController() 127 | elif sys.argv[1] == '2': 128 | thingInControl = ReactorSlaveController() 129 | else: 130 | thingInControl = LoopingCallController() 131 | 132 | thingInControl.run() 133 | 134 | print server 135 | 136 | print 'end' 137 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | authentication 2 | making your own system? you have two choices: 3 | * be smarter than everyone else in the world 4 | * be smarter than that finite set of people that will play your game 5 | 6 | 7 | protocol: 8 | Twisted needs to have another Twisted client at the other end 9 | This may not be realistic 10 | But let's use Twisted at both ends because we're trying to do it rapidly 11 | 12 | Ok, which chapter should be first? 13 | I tried considering the "Messages across the wire" as the first chapter, but I think my brain wants to do first things first. 14 | 15 | ---- Chapter 0 ---- 16 | 17 | Talk about Rapid Development 18 | 19 | ---- End Chapter 0 ---- 20 | 21 | ---- Chapter 1 ---- 22 | First: 23 | simple pygame example - Chimp 24 | code example. 25 | critique about how this will fail under complexity 26 | * maybe some pseudocode with a hella nested if/else 27 | critique about how this will fail under network 28 | * maybe the same example with a sleep(1) simulating the network 29 | 30 | Games need a front end with NO perceptible lag for user interaction / feedback. 31 | 32 | Model / view / controller with Event-driven can solve these two design problems 33 | 34 | Explain MVC 35 | Code example 36 | 37 | Then we will also need to handle multiple Views and Controllers. 38 | 39 | Explain Event-driven 40 | Code example 41 | ---- End Chapter 1 ---- 42 | 43 | ---- Chapter 2 ---- 44 | 45 | Strategy A: consumers can nullify events 46 | case 1: 47 | event comes in 48 | click on blank part of screen 49 | traps each get a crack at it, but do nothing 50 | some 'other' entity must turn it into a new trap placement AFTER traps have a crack 51 | 52 | case 2: 53 | event comes in 54 | click on trap 55 | traps each get a crack at it, one catches it 56 | this trap must change event.removeMe = True otherwise the 'other' entity will 57 | add a trap to the blank part of the screen 58 | 59 | Pros: 60 | Cons: the "AFTER" is tricky and requires complexity in the events module 61 | 62 | Strategy B: event gets classified by an outside entity, then gets put on the front 63 | of the queue. 64 | case 1: 65 | event comes in 66 | click on blank part of screen 67 | traps don't handle raw mouseclicks 68 | outside entity catches mouseclick, goes through each trap, sees if it collides, it doesn't, so post_at_front() a 'ScreenClick' event. 69 | 70 | case 2: 71 | event comes in 72 | click on trap 73 | traps don't handle raw mouseclicks 74 | outside entity catches mouseclick, goes through each trap, sees if it collides, it does, so post_at_front() a 'TrapClick' event. 75 | 76 | Pros: sets the design up for a separate GUI Model 77 | Cons: multiplies the kinds of events there are, requires an events module API addition, post_at_front() 78 | 79 | ---- End Chapter 2 ---- 80 | 81 | 82 | Should I do some kind of *radical* client-server? 83 | Like, start the UI, the first thing it does is ask the EventManager, "Who are the players?", "What's the game state?", maybe even "Here are my capabilities". 84 | 85 | Hmm. the "here are my capabilities" part will probably mean more code. Scratch that. 86 | 87 | Anyway, the idea here is that the view should not respond to BoardBuiltEvent as much as it should broadcast a query (with a TTL) like "Hey guys, send me the board object" 88 | 89 | This approach keeps the data inside the messages, less in shared state. Synchronization will turn into scaling issues. 90 | 91 | 92 | ---- 93 | 94 | I should have a section on terminology like "main loop" 95 | 96 | I should have a section on what "rapid development" means -- chosing from alternatives the one which gives us the highest coding speed result, often over program efficiency. 97 | -------------------------------------------------------------------------------- /presentation_code_structure.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/presentation_code_structure.odp -------------------------------------------------------------------------------- /pygame_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ''' 3 | An example of using the collision_resolver module 4 | 5 | Pops up a window populated by randomly placed little squares. 6 | You control the big square with the direction keys. 7 | ''' 8 | 9 | from random import randint 10 | import pygame 11 | from pygame.locals import * 12 | 13 | RESOLUTION = (600,400) 14 | green = (5,255,5) 15 | avatar = None 16 | avatarGroup = None 17 | sceen = None 18 | background = None 19 | origBackground = None 20 | screen = None 21 | 22 | def main(): 23 | clock = pygame.time.Clock() 24 | 25 | prepare() 26 | while True: 27 | clock.tick(2) 28 | retval = iterate() 29 | if not retval: 30 | return 31 | 32 | def prepare(): 33 | global screen 34 | pygame.init() 35 | screen = pygame.display.set_mode(RESOLUTION) 36 | Start() 37 | 38 | def iterate(): 39 | avatar.moveState[0] = randint(-9,9) 40 | avatar.moveState[1] = randint(-9,9) 41 | 42 | for ev in pygame.event.get(): 43 | if ev.type == QUIT: 44 | return False 45 | if ev.type == KEYDOWN and ev.key == K_ESCAPE: 46 | return False 47 | 48 | #clear 49 | avatarGroup.clear( screen, background ) 50 | 51 | #update 52 | avatarGroup.update() 53 | 54 | #draw 55 | avatarGroup.draw(screen) 56 | pygame.display.update() 57 | 58 | return True 59 | 60 | def Start(): 61 | global avatar, avatarGroup, background, origBackground, screen 62 | background = pygame.Surface( RESOLUTION ) 63 | off_black = (40,10,0) 64 | background.fill( off_black ) 65 | 66 | # avatar will be a green square in the center of the screen 67 | avatar = Avatar() 68 | 69 | fixedBackgroundSprites = pygame.sprite.Group() 70 | for block in GenerateRandomBlocks(30, RESOLUTION): 71 | if not block.rect.colliderect(avatar.rect): 72 | fixedBackgroundSprites.add( block ) 73 | fixedBackgroundSprites.draw( background ) 74 | 75 | avatar.collidables = fixedBackgroundSprites 76 | 77 | avatarGroup = pygame.sprite.Group() 78 | avatarGroup.add( avatar ) 79 | 80 | screen.blit( background, (0,0) ) 81 | pygame.display.flip() 82 | origBackground = background.copy() 83 | 84 | 85 | class Avatar(pygame.sprite.Sprite): 86 | def __init__(self): 87 | pygame.sprite.Sprite.__init__(self) 88 | self.image = pygame.Surface( (100,100) ) 89 | self.image.fill( green ) 90 | self.rect = self.image.get_rect() 91 | self.rect.center = (RESOLUTION[0]/2, RESOLUTION[1]/2) 92 | self.moveState = [0,0] 93 | self.collidables = None 94 | 95 | def update(self): 96 | if self.moveState[0] or self.moveState[1]: 97 | screen = pygame.display.get_surface() 98 | screen.blit( origBackground, (0,0) ) 99 | self.rect.move_ip(*self.moveState) 100 | 101 | class SimpleSprite(pygame.sprite.Sprite): 102 | def __init__(self, surface): 103 | pygame.sprite.Sprite.__init__(self) 104 | self.image = surface 105 | self.rect = self.image.get_rect() 106 | 107 | def GenerateRandomBlocks( howMany, positionBounds ): 108 | lowerColorBound = (100,100,100) 109 | upperColorBound = (200,200,200) 110 | 111 | lowerXBound, lowerYBound = 0,0 112 | upperXBound, upperYBound = positionBounds 113 | 114 | lowerWidthBound, lowerHeightBound = 30,30 115 | upperWidthBound, upperHeightBound = 60,60 116 | 117 | for i in range(howMany): 118 | color = [ randint(lowerColorBound[i],upperColorBound[i]) 119 | for i in range(3) ] 120 | pos = [ randint(lowerXBound, upperXBound), 121 | randint(lowerYBound,upperYBound) ] 122 | size = [ randint(lowerWidthBound, upperWidthBound), 123 | randint(lowerHeightBound, upperHeightBound) ] 124 | 125 | s = SimpleSprite( pygame.Surface(size) ) 126 | s.image.fill( color ) 127 | s.rect.topleft = pos 128 | yield s 129 | 130 | if __name__ == '__main__': 131 | main() 132 | -------------------------------------------------------------------------------- /screenshot-example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/screenshot-example1.png -------------------------------------------------------------------------------- /screenshot-example4a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/screenshot-example4a.png -------------------------------------------------------------------------------- /screenshot-example4b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/screenshot-example4b.png -------------------------------------------------------------------------------- /server_snippet.txt.html: -------------------------------------------------------------------------------- 1 |  $ python server1.py 2 | Game Start Request 3 | Map Finished Building Event 4 | Game Started Event 5 | Charactor Placement Event  at  <__main__.Sector instance at 0xc9b290> 6 | Charactor Move Request 7 | Charactor Move Request 8 | Charactor Move Event  to  <__main__.Sector instance at 0xc9b320> 9 | Charactor Move Request 10 | Charactor Move Event  to  <__main__.Sector instance at 0xc9b290> 11 | Charactor Move Request 12 | Charactor Move Event  to  <__main__.Sector instance at 0xc9b3b0> 13 | -------------------------------------------------------------------------------- /table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 |

sjbrown's Writing Games Tutorial

19 |

Purpose

20 |

Twitch vs. Non-Twitch

21 |

What You Should Know

22 |

Object Oriented Programming

23 |

Design Patterns

24 |

PART 1

25 |

Example Goal

26 |

The Architecture

27 |

Model View Controller

28 |

Mediator

29 |

The Game Model

30 |

Game

31 |

Player

32 |

Charactor

33 |

Map

34 |

Sector

35 |

Location

36 |

Item

37 |

Our Example

38 |

PART 2

39 |

Internet Play

40 |

Synchronous / Asynchronous

41 |

Implementation

42 |

King of the Castle

43 |

Messages Over the Wire

44 |

More Problems

45 |

Multiplayer

46 |

Reconnecting After A Drop

47 |

PART 3

48 |

Graphical User Interface

49 |

What Is A Widget

50 |

GUI Screens

51 |

FAQ

52 |

Translations

53 | -------------------------------------------------------------------------------- /test_chapter.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjbrown/writing_games_tutorial/9aebc7669cd927490874a86358a705526c1a9c6d/test_chapter.odt --------------------------------------------------------------------------------