snipes-game 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jerry Westrick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: snipes-game
3
+ Version: 0.1.0
4
+ Summary: A recreation of the classic 1983 Novell NetWare game Snipes
5
+ Author: Jerry Westrick
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/JerryWestrick/snipes
8
+ Project-URL: Repository, https://github.com/JerryWestrick/snipes
9
+ Project-URL: Issues, https://github.com/JerryWestrick/snipes/issues
10
+ Keywords: game,pygame,retro,arcade,snipes,novell
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Games/Entertainment :: Arcade
19
+ Classifier: Operating System :: OS Independent
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pygame-ce>=2.0
24
+ Dynamic: license-file
25
+
26
+ # Snipes
27
+
28
+ A Python/Pygame recreation of the classic 1983 Novell NetWare game.
29
+
30
+ The original Snipes was one of the first network games for personal computers, created by Drew Major, Dale Neibaur, and Kyle Powell to test and debug Novell's networking software. This version brings the gameplay to modern systems using Pygame.
31
+
32
+ ## Gameplay
33
+
34
+ Navigate a randomly generated maze, destroy enemy hives and the snipes they spawn. Clear all hives and snipes to advance to the next level.
35
+
36
+ - **9 levels** with increasingly larger mazes, more hives, faster and more numerous enemies
37
+ - **8-direction shooting** — combine two keys for diagonal shots
38
+ - **Diagonal shots ricochet** off walls — bank shots around corners
39
+ - Score and lives carry across levels
40
+
41
+ ## Controls
42
+
43
+ | Keys | Action |
44
+ |------|--------|
45
+ | Arrow keys | Move |
46
+ | W A S D | Shoot (combine for diagonals) |
47
+ | P | Pause |
48
+ | ESC | Quit |
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install snipe
54
+ ```
55
+
56
+ Or from source:
57
+
58
+ ```bash
59
+ git clone https://github.com/JerryWestrick/snipes.git
60
+ cd snipes
61
+ pip install .
62
+ ```
63
+
64
+ ## Run
65
+
66
+ ```bash
67
+ snipe
68
+ ```
69
+
70
+ Or:
71
+
72
+ ```bash
73
+ python -m snipe
74
+ ```
75
+
76
+ ## Requirements
77
+
78
+ - Python 3.10+
79
+ - pygame-ce
@@ -0,0 +1,54 @@
1
+ # Snipes
2
+
3
+ A Python/Pygame recreation of the classic 1983 Novell NetWare game.
4
+
5
+ The original Snipes was one of the first network games for personal computers, created by Drew Major, Dale Neibaur, and Kyle Powell to test and debug Novell's networking software. This version brings the gameplay to modern systems using Pygame.
6
+
7
+ ## Gameplay
8
+
9
+ Navigate a randomly generated maze, destroy enemy hives and the snipes they spawn. Clear all hives and snipes to advance to the next level.
10
+
11
+ - **9 levels** with increasingly larger mazes, more hives, faster and more numerous enemies
12
+ - **8-direction shooting** — combine two keys for diagonal shots
13
+ - **Diagonal shots ricochet** off walls — bank shots around corners
14
+ - Score and lives carry across levels
15
+
16
+ ## Controls
17
+
18
+ | Keys | Action |
19
+ |------|--------|
20
+ | Arrow keys | Move |
21
+ | W A S D | Shoot (combine for diagonals) |
22
+ | P | Pause |
23
+ | ESC | Quit |
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install snipe
29
+ ```
30
+
31
+ Or from source:
32
+
33
+ ```bash
34
+ git clone https://github.com/JerryWestrick/snipes.git
35
+ cd snipes
36
+ pip install .
37
+ ```
38
+
39
+ ## Run
40
+
41
+ ```bash
42
+ snipe
43
+ ```
44
+
45
+ Or:
46
+
47
+ ```bash
48
+ python -m snipe
49
+ ```
50
+
51
+ ## Requirements
52
+
53
+ - Python 3.10+
54
+ - pygame-ce
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "snipes-game"
7
+ version = "0.1.0"
8
+ description = "A recreation of the classic 1983 Novell NetWare game Snipes"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Jerry Westrick"},
14
+ ]
15
+ keywords = ["game", "pygame", "retro", "arcade", "snipes", "novell"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: End Users/Desktop",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Games/Entertainment :: Arcade",
25
+ "Operating System :: OS Independent",
26
+ ]
27
+ dependencies = [
28
+ "pygame-ce>=2.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/JerryWestrick/snipes"
33
+ Repository = "https://github.com/JerryWestrick/snipes"
34
+ Issues = "https://github.com/JerryWestrick/snipes/issues"
35
+
36
+ [project.scripts]
37
+ snipe = "snipe.main:main"
38
+
39
+ [tool.setuptools.packages.find]
40
+ include = ["snipe*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Snipe — a recreation of the classic 1983 Novell NetWare game."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow running with: python -m snipe"""
2
+
3
+ from snipe.main import Game
4
+
5
+ game = Game()
6
+ game.run()
@@ -0,0 +1,88 @@
1
+ """Snipe AI behavior."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import random
6
+ import math
7
+ from snipe.entities import Snipe, Bullet
8
+ from snipe.settings import DIRECTIONS, SNIPE_SHOOT_CHANCE
9
+ from snipe.physics import move_circle
10
+
11
+
12
+ _DIR_NAMES = list(DIRECTIONS.keys())
13
+
14
+
15
+ def update_snipe(snipe: Snipe, player_x: float, player_y: float,
16
+ dt: float, walls: list, rng: random.Random) -> Bullet | None:
17
+ """Update a snipe's movement and maybe shoot. Returns a bullet or None."""
18
+ if not snipe.alive:
19
+ return None
20
+
21
+ # Movement timer
22
+ snipe.move_timer += dt
23
+ if snipe.move_timer >= snipe.move_interval:
24
+ snipe.move_timer = 0.0
25
+ snipe.direction = _pick_direction(snipe, player_x, player_y, rng)
26
+
27
+ # Move in current direction
28
+ dvx, dvy = DIRECTIONS[snipe.direction]
29
+ dx = dvx * snipe.speed * dt
30
+ dy = dvy * snipe.speed * dt
31
+ snipe.x, snipe.y = move_circle(snipe.x, snipe.y, dx, dy, snipe.radius, walls)
32
+
33
+ # Maybe shoot
34
+ if rng.random() < SNIPE_SHOOT_CHANCE:
35
+ return _shoot_at_player(snipe, player_x, player_y)
36
+ return None
37
+
38
+
39
+ def _pick_direction(snipe: Snipe, player_x: float, player_y: float,
40
+ rng: random.Random) -> str:
41
+ """Pick a direction biased toward the player."""
42
+ dx = player_x - snipe.x
43
+ dy = player_y - snipe.y
44
+
45
+ # Target direction components
46
+ tx = 1 if dx > 0 else (-1 if dx < 0 else 0)
47
+ ty = 1 if dy > 0 else (-1 if dy < 0 else 0)
48
+
49
+ # Weight directions toward player
50
+ candidates = []
51
+ for name, (dvx, dvy) in DIRECTIONS.items():
52
+ weight = 1
53
+ # Normalize diagonal components for comparison
54
+ sx = 1 if dvx > 0 else (-1 if dvx < 0 else 0)
55
+ sy = 1 if dvy > 0 else (-1 if dvy < 0 else 0)
56
+ if sx == tx and tx != 0:
57
+ weight += 2
58
+ if sy == ty and ty != 0:
59
+ weight += 2
60
+ candidates.extend([name] * weight)
61
+
62
+ return rng.choice(candidates)
63
+
64
+
65
+ def _shoot_at_player(snipe: Snipe, player_x: float, player_y: float) -> Bullet:
66
+ """Shoot toward the player using the nearest of 8 directions."""
67
+ dx = player_x - snipe.x
68
+ dy = player_y - snipe.y
69
+ angle = math.atan2(dy, dx)
70
+
71
+ # Find nearest of 8 directions
72
+ best_dir = "N"
73
+ best_diff = float("inf")
74
+ for name, (dvx, dvy) in DIRECTIONS.items():
75
+ dir_angle = math.atan2(dvy, dvx)
76
+ diff = abs(angle - dir_angle)
77
+ if diff > math.pi:
78
+ diff = 2 * math.pi - diff
79
+ if diff < best_diff:
80
+ best_diff = diff
81
+ best_dir = name
82
+
83
+ return Bullet(
84
+ x=snipe.x,
85
+ y=snipe.y,
86
+ direction=best_dir,
87
+ owner="snipe",
88
+ )
@@ -0,0 +1,30 @@
1
+ """Viewport camera centered on the player."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from snipe.settings import SCREEN_WIDTH, SCREEN_HEIGHT
6
+
7
+
8
+ class Camera:
9
+ """Tracks a target position and provides an offset for rendering."""
10
+
11
+ def __init__(self, maze_width: float, maze_height: float):
12
+ self.maze_width = maze_width
13
+ self.maze_height = maze_height
14
+ self.x: float = 0
15
+ self.y: float = 0
16
+
17
+ def update(self, target_x: float, target_y: float) -> None:
18
+ """Center the camera on the target."""
19
+ self.x = target_x - SCREEN_WIDTH / 2
20
+ self.y = target_y - SCREEN_HEIGHT / 2
21
+
22
+ def apply(self, x: float, y: float) -> tuple[float, float]:
23
+ """Convert world coordinates to screen coordinates."""
24
+ return (x - self.x, y - self.y)
25
+
26
+ def is_visible(self, x: float, y: float, margin: float = 50) -> bool:
27
+ """Check if a world position is visible on screen."""
28
+ sx, sy = self.apply(x, y)
29
+ return (-margin < sx < SCREEN_WIDTH + margin and
30
+ -margin < sy < SCREEN_HEIGHT + margin)
@@ -0,0 +1,71 @@
1
+ """Game entities — all positioned with float coordinates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from snipe.settings import (
7
+ PLAYER_RADIUS, PLAYER_SPEED, PLAYER_START_LIVES,
8
+ SNIPE_RADIUS, SNIPE_SPEED, SNIPE_MOVE_INTERVAL,
9
+ HIVE_RADIUS,
10
+ BULLET_RADIUS, BULLET_SPEED, BULLET_MAX_AGE,
11
+ DIRECTIONS,
12
+ )
13
+
14
+
15
+ @dataclass
16
+ class Player:
17
+ x: float
18
+ y: float
19
+ radius: float = PLAYER_RADIUS
20
+ speed: float = PLAYER_SPEED
21
+ lives: int = PLAYER_START_LIVES
22
+ alive: bool = True
23
+ invulnerable: float = 0.0 # seconds remaining
24
+
25
+ @property
26
+ def is_invulnerable(self) -> bool:
27
+ return self.invulnerable > 0
28
+
29
+
30
+ @dataclass
31
+ class Bullet:
32
+ x: float
33
+ y: float
34
+ direction: str # one of the 8 direction keys: "N", "NE", etc.
35
+ owner: str # "player" or "snipe"
36
+ radius: float = BULLET_RADIUS
37
+ speed: float = BULLET_SPEED
38
+ age: float = 0.0
39
+ max_age: float = BULLET_MAX_AGE
40
+ alive: bool = True
41
+
42
+ @property
43
+ def vx(self) -> float:
44
+ return DIRECTIONS[self.direction][0] * self.speed
45
+
46
+ @property
47
+ def vy(self) -> float:
48
+ return DIRECTIONS[self.direction][1] * self.speed
49
+
50
+
51
+ @dataclass
52
+ class Snipe:
53
+ x: float
54
+ y: float
55
+ direction: str = "N"
56
+ radius: float = SNIPE_RADIUS
57
+ speed: float = SNIPE_SPEED
58
+ alive: bool = True
59
+ move_timer: float = 0.0
60
+ move_interval: float = SNIPE_MOVE_INTERVAL
61
+
62
+
63
+ @dataclass
64
+ class Hive:
65
+ x: float
66
+ y: float
67
+ radius: float = HIVE_RADIUS
68
+ health: int = 1
69
+ alive: bool = True
70
+ spawn_timer: float = 0.0
71
+ spawn_interval: float = 6.0