Žádný typy, pouze inty

This commit is contained in:
Tomáš Sláma 2025-02-11 21:50:20 +01:00
parent f8d03c6162
commit 6ffbfb0d1e
3 changed files with 101 additions and 119 deletions
asteracer-python
asteracer-rust/src

View file

@ -1,3 +1,4 @@
"""The Asteracer game implementation. Includes the base movement code + couple of QOL additions (eg. save states)."""
from __future__ import annotations from __future__ import annotations
import dataclasses import dataclasses
@ -5,14 +6,6 @@ import random
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from math import isqrt from math import isqrt
from typing import List, Union, Tuple, Dict
# tohle dříve byly numpy typy, ale asi je lepší,
# ať účastníci nemusí nic instalovat...
InstType = int # np.int8
PosType = int # np.int64
SpeedType = int # np.int64
SizeType = int # np.int64
class TickFlag: class TickFlag:
@ -23,18 +16,18 @@ class TickFlag:
@dataclass @dataclass
class Racer: class Racer:
x: PosType = 0 x: int = 0
y: PosType = 0 y: int = 0
vx: SpeedType = 0 vx: int = 0
vy: SpeedType = 0 vy: int = 0
radius: SizeType = 1 radius: int = 1
@dataclass(frozen=True) @dataclass(frozen=True)
class Asteroid: class Asteroid:
x: PosType = 0 x: int = 0
y: PosType = 0 y: int = 0
radius: SizeType = 1 radius: int = 1
Goal = Asteroid Goal = Asteroid
@ -42,7 +35,7 @@ Goal = Asteroid
class Instruction: class Instruction:
MAX_ACCELERATION = 127 MAX_ACCELERATION = 127
def __init__(self, vx: Union[int, float] = 0, vy: Union[int, float] = 0): def __init__(self, vx: int | float = 0, vy: int | float = 0):
"""Whatever values we get, normalize them.""" """Whatever values we get, normalize them."""
if distance_squared(vx, vy) > Instruction.MAX_ACCELERATION ** 2: if distance_squared(vx, vy) > Instruction.MAX_ACCELERATION ** 2:
@ -62,8 +55,8 @@ class Instruction:
assert distance_squared(vx, vy) <= Instruction.MAX_ACCELERATION ** 2 assert distance_squared(vx, vy) <= Instruction.MAX_ACCELERATION ** 2
self.vx = InstType(vx) self.vx = vx
self.vy = InstType(vy) self.vy = vy
def __hash__(self): def __hash__(self):
return hash((self.vx, self.vy)) return hash((self.vx, self.vy))
@ -90,26 +83,26 @@ class Instruction:
@dataclass @dataclass
class BoundingBox: class BoundingBox:
min_x: PosType min_x: int
min_y: PosType min_y: int
max_x: PosType max_x: int
max_y: PosType max_y: int
def width(self) -> SizeType: def width(self) -> int:
return SizeType(self.max_x - self.min_x) return int(self.max_x - self.min_x)
def height(self) -> SizeType: def height(self) -> int:
return SizeType(self.max_y - self.min_y) return int(self.max_y - self.min_y)
def distance_squared(x1, y1, x2=0, y2=0) -> PosType: def distance_squared(x1, y1, x2=0, y2=0) -> int:
"""Squared Euclidean distance between two points.""" """Squared Euclidean distance between two points."""
return (PosType(x1) - PosType(x2)) ** 2 + (PosType(y1) - PosType(y2)) ** 2 return (int(x1) - int(x2)) ** 2 + (int(y1) - int(y2)) ** 2
def euclidean_distance(x1, y1, x2=0, y2=0): def euclidean_distance(x1, y1, x2=0, y2=0):
"""Integer Euclidean distance between two points. Uses integer square root.""" """Integer Euclidean distance between two points. Uses integer square root."""
return PosType(isqrt(distance_squared(x1, y1, x2, y2))) return int(isqrt(distance_squared(x1, y1, x2, y2)))
def signum(x): def signum(x):
@ -132,8 +125,8 @@ class Simulation:
def __init__( def __init__(
self, self,
racer: Racer = Racer(), racer: Racer = Racer(),
asteroids: List[Asteroid] = None, asteroids: list[Asteroid] = None,
goals: List[Goal] = None, goals: list[Goal] = None,
bounding_box: BoundingBox = None, bounding_box: BoundingBox = None,
): ):
# the initial racer state (used when resetting the simulation) # the initial racer state (used when resetting the simulation)
@ -146,7 +139,7 @@ class Simulation:
# to speed up the computation, we divide the bounding box (if we have one) into a grid # to speed up the computation, we divide the bounding box (if we have one) into a grid
# we do this so we don't need to check all asteroids at each tick, only those that could collide with the racer # we do this so we don't need to check all asteroids at each tick, only those that could collide with the racer
self._grid: Dict[Tuple[int, int], List[Asteroid]] = defaultdict(list) self._grid: dict[tuple[int, int], list[Asteroid]] = defaultdict(list)
for asteroid in asteroids: for asteroid in asteroids:
min_x, min_y = self._coordinate_to_grid( min_x, min_y = self._coordinate_to_grid(
@ -163,12 +156,12 @@ class Simulation:
for grid_y in range(min_y, max_y + 1): for grid_y in range(min_y, max_y + 1):
self._grid[(grid_x, grid_y)].append(asteroid) self._grid[(grid_x, grid_y)].append(asteroid)
self.reached_goals: List[bool] = [False] * len(self.goals) self.reached_goals: list[bool] = [False] * len(self.goals)
# a list of simulation states that can be popped (restored to) # a list of simulation states that can be popped (restored to)
self._pushed_states = [] self._pushed_states = []
def _coordinate_to_grid(self, x: float, y: float) -> Tuple[int, int]: def _coordinate_to_grid(self, x: float, y: float) -> tuple[int, int]:
"""Translate an (x,y) coordinate into a coordinate of the grid.""" """Translate an (x,y) coordinate into a coordinate of the grid."""
return (x // self.CELL_SIZE, y // self.CELL_SIZE) return (x // self.CELL_SIZE, y // self.CELL_SIZE)
@ -181,14 +174,14 @@ class Simulation:
self.racer.vy = division(self.racer.vy * self.DRAG_FRACTION[0], self.DRAG_FRACTION[1]) self.racer.vy = division(self.racer.vy * self.DRAG_FRACTION[0], self.DRAG_FRACTION[1])
# velocity # velocity
self.racer.vx += SpeedType(vx) self.racer.vx += int(vx)
self.racer.vy += SpeedType(vy) self.racer.vy += int(vy)
# movement # movement
self.racer.x += self.racer.vx self.racer.x += self.racer.vx
self.racer.y += self.racer.vy self.racer.y += self.racer.vy
def _push_out(self, obj: Union[Asteroid, BoundingBox]) -> bool: def _push_out(self, obj: Asteroid | BoundingBox) -> bool:
"""Attempt to push the racer out of the object (if he's colliding), adjusting """Attempt to push the racer out of the object (if he's colliding), adjusting
his velocity accordingly (based on the angle of collision). Returns True if the his velocity accordingly (based on the angle of collision). Returns True if the
racer was pushed out, otherwise returns False.""" racer was pushed out, otherwise returns False."""
@ -292,7 +285,7 @@ class Simulation:
return (TickFlag.COLLIDED if collided else 0) | (TickFlag.GOAL_REACHED if goal else 0) return (TickFlag.COLLIDED if collided else 0) | (TickFlag.GOAL_REACHED if goal else 0)
def simulate(self, instructions: List[Instruction]): def simulate(self, instructions: list[Instruction]):
"""Simulate a number of instructions for the simulation (from the start).""" """Simulate a number of instructions for the simulation (from the start)."""
self.restart() self.restart()
@ -337,10 +330,10 @@ class Simulation:
lines = f.read().splitlines() lines = f.read().splitlines()
racer_parts = lines[0].split() racer_parts = lines[0].split()
racer = Racer(x=PosType(racer_parts[0]), y=PosType(racer_parts[1]), radius=SizeType(racer_parts[2])) racer = Racer(x=int(racer_parts[0]), y=int(racer_parts[1]), radius=int(racer_parts[2]))
bb_parts = lines[1].split() bb_parts = lines[1].split()
bb = BoundingBox(PosType(bb_parts[0]), PosType(bb_parts[1]), PosType(bb_parts[2]), PosType(bb_parts[2])) bb = BoundingBox(int(bb_parts[0]), int(bb_parts[1]), int(bb_parts[2]), int(bb_parts[2]))
asteroid_count = int(lines[2]) asteroid_count = int(lines[2])
@ -349,9 +342,9 @@ class Simulation:
asteroid_parts = lines[i].split() asteroid_parts = lines[i].split()
asteroids.append( asteroids.append(
Asteroid( Asteroid(
x=PosType(asteroid_parts[0]), x=int(asteroid_parts[0]),
y=PosType(asteroid_parts[1]), y=int(asteroid_parts[1]),
radius=SizeType(asteroid_parts[2]), radius=int(asteroid_parts[2]),
) )
) )
@ -362,9 +355,9 @@ class Simulation:
goal_parts = lines[i].split() goal_parts = lines[i].split()
goals.append( goals.append(
Asteroid( Asteroid(
x=PosType(goal_parts[0]), x=int(goal_parts[0]),
y=PosType(goal_parts[1]), y=int(goal_parts[1]),
radius=SizeType(goal_parts[2]), radius=int(goal_parts[2]),
) )
) )
@ -390,7 +383,7 @@ class Simulation:
self.reached_goals = list(self._pushed_states[-1][1]) self.reached_goals = list(self._pushed_states[-1][1])
def save_instructions(path: str, instructions: List[Instruction]): def save_instructions(path: str, instructions: list[Instruction]):
"""Save a list of instructions to a file: """Save a list of instructions to a file:
| 4 // number if instructions | 4 // number if instructions
| -16 -127 // instructions... | -16 -127 // instructions...
@ -405,13 +398,13 @@ def save_instructions(path: str, instructions: List[Instruction]):
f.write(f"{instruction.vx} {instruction.vy}\n") f.write(f"{instruction.vx} {instruction.vy}\n")
def load_instructions(path: str) -> List[Instruction]: def load_instructions(path: str) -> list[Instruction]:
"""Load a list of instructions from a file (see save_instructions for the format description).""" """Load a list of instructions from a file (see save_instructions for the format description)."""
instructions = [] instructions = []
with open(path) as f: with open(path) as f:
for line in f.read().splitlines()[1:]: for line in f.read().splitlines()[1:]:
instruction_parts = list(map(InstType, line.split())) instruction_parts = list(map(int, line.split()))
instructions.append(Instruction(*instruction_parts)) instructions.append(Instruction(*instruction_parts))
return instructions return instructions

View file

@ -12,61 +12,50 @@ pub mod TickFlag {
pub type TickResult = usize; pub type TickResult = usize;
pub type InstType = i8; pub static MAX_ACCELERATION: isize = 127;
pub type PosType = i64;
pub type SpeedType = i64;
pub type SizeType = i64;
pub static MAX_ACCELERATION: InstType = 127; pub static DRAG_FRACTION: (isize, isize) = (9, 10);
pub static COLLISION_FRACTION: (isize, isize) = (1, 2);
pub static DRAG_FRACTION: (SpeedType, SpeedType) = (9, 10);
pub static COLLISION_FRACTION: (SpeedType, SpeedType) = (1, 2);
pub static MAX_COLLISION_RESOLUTIONS: usize = 5; pub static MAX_COLLISION_RESOLUTIONS: usize = 5;
pub static CELL_SIZE: PosType = 10_000; pub static CELL_SIZE: isize = 10_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Racer { pub struct Racer {
pub x: PosType, pub x: isize,
pub y: PosType, pub y: isize,
pub vx: SpeedType, pub vx: isize,
pub vy: SpeedType, pub vy: isize,
pub radius: SizeType, pub radius: isize,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Asteroid { pub struct Asteroid {
pub x: PosType, pub x: isize,
pub y: PosType, pub y: isize,
pub radius: SizeType, pub radius: isize,
} }
pub type Goal = Asteroid; pub type Goal = Asteroid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Instruction { pub(crate) struct Instruction {
pub vx: InstType, pub vx: isize,
pub vy: InstType, pub vy: isize,
} }
impl Instruction { impl Instruction {
fn valid(vx: PosType, vy: PosType) -> bool { fn valid(vx: isize, vy: isize) -> bool {
distance_squared(vx, vy, 0, 0) <= (MAX_ACCELERATION as PosType).pow(2) distance_squared(vx, vy, 0, 0) <= (MAX_ACCELERATION).pow(2)
} }
pub fn new<T>(vx: T, vy: T) -> Self pub fn new(vx: isize, vy: isize) -> Self {
where
T: Copy + Into<PosType>,
{
let vx: i64 = vx.into();
let vy: i64 = vy.into();
if !Self::valid(vx, vy) { if !Self::valid(vx, vy) {
// use float to properly normalize here // use float to properly normalize here
let float_distance = ((vx as f64).powf(2.) + (vy as f64).powf(2.)).powf(1. / 2.); let float_distance = ((vx as f64).powf(2.) + (vy as f64).powf(2.)).powf(1. / 2.);
let mut vx = ((vx as f64 / float_distance) * MAX_ACCELERATION as f64) as PosType; let mut vx = ((vx as f64 / float_distance) * MAX_ACCELERATION as f64) as isize;
let mut vy = ((vy as f64 / float_distance) * MAX_ACCELERATION as f64) as PosType; let mut vy = ((vy as f64 / float_distance) * MAX_ACCELERATION as f64) as isize;
// if we're still over, decrement both values // if we're still over, decrement both values
if !Self::valid(vx, vy) { if !Self::valid(vx, vy) {
@ -74,14 +63,14 @@ impl Instruction {
vy -= vy.signum(); vy -= vy.signum();
} }
return Self { vx: vx as InstType, vy: vy as InstType }; return Self { vx, vy };
} }
assert!(Self::valid(vx, vy)); assert!(Self::valid(vx, vy));
Self { Self {
vx: vx as InstType, vx,
vy: vy as InstType, vy,
} }
} }
@ -89,8 +78,8 @@ impl Instruction {
let mut rng = rand::rng(); let mut rng = rand::rng();
Self { Self {
vx: rng.random::<InstType>(), vx: rng.random::<i64>() as isize,
vy: rng.random::<InstType>(), vy: rng.random::<i64>() as isize,
} }
} }
@ -110,8 +99,8 @@ impl Instruction {
.collect::<Vec<&str>>(); .collect::<Vec<&str>>();
instructions.push(Instruction { instructions.push(Instruction {
vx: parts[0].parse::<InstType>().unwrap(), vx: parts[0].parse::<isize>().unwrap(),
vy: parts[1].parse::<InstType>().unwrap(), vy: parts[1].parse::<isize>().unwrap(),
}) })
} }
@ -130,24 +119,24 @@ impl Instruction {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct BoundingBox { pub struct BoundingBox {
pub min_x: SizeType, pub min_x: isize,
pub min_y: SizeType, pub min_y: isize,
pub max_x: SizeType, pub max_x: isize,
pub max_y: SizeType, pub max_y: isize,
} }
impl BoundingBox { impl BoundingBox {
pub fn width(&self) -> SizeType { pub fn width(&self) -> isize {
self.max_x - self.min_x self.max_x - self.min_x
} }
pub fn height(&self) -> SizeType { pub fn height(&self) -> isize {
self.max_y - self.min_y self.max_y - self.min_y
} }
} }
/// Squared Euclidean distance; useful for distance checks. /// Squared Euclidean distance; useful for distance checks.
fn distance_squared(x1: PosType, y1: PosType, x2: PosType, y2: PosType) -> PosType { fn distance_squared(x1: isize, y1: isize, x2: isize, y2: isize) -> isize {
(x1 - x2).pow(2) + (y1 - y2).pow(2) (x1 - x2).pow(2) + (y1 - y2).pow(2)
} }
@ -155,8 +144,8 @@ fn distance_squared(x1: PosType, y1: PosType, x2: PosType, y2: PosType) -> PosTy
/// ///
/// Note: this implementation might break for larger position values, but since /// Note: this implementation might break for larger position values, but since
/// the maps are never going to be this large, I'm not fixing it now. /// the maps are never going to be this large, I'm not fixing it now.
pub fn euclidean_distance(x1: PosType, y1: PosType, x2: PosType, y2: PosType) -> PosType { pub fn euclidean_distance(x1: isize, y1: isize, x2: isize, y2: isize) -> isize {
(distance_squared(x1, y1, x2, y2) as f64).sqrt() as PosType (distance_squared(x1, y1, x2, y2) as f64).sqrt() as isize
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -170,8 +159,8 @@ pub struct Simulation {
pub reached_goals: Vec<bool>, pub reached_goals: Vec<bool>,
_grid: HashMap<(PosType, PosType), Vec<Asteroid>>, _grid: HashMap<(isize, isize), Vec<Asteroid>>,
_cell_size: PosType, _cell_size: isize,
} }
/// ///
@ -234,7 +223,7 @@ impl Simulation {
simulation simulation
} }
fn coordinate_to_grid(&self, x: PosType, y: PosType) -> (PosType, PosType) { fn coordinate_to_grid(&self, x: isize, y: isize) -> (isize, isize) {
(x / self._cell_size, y / self._cell_size) (x / self._cell_size, y / self._cell_size)
} }
@ -242,11 +231,11 @@ impl Simulation {
self.racer.vx = (self.racer.vx * DRAG_FRACTION.0) / DRAG_FRACTION.1; self.racer.vx = (self.racer.vx * DRAG_FRACTION.0) / DRAG_FRACTION.1;
self.racer.vy = (self.racer.vy * DRAG_FRACTION.0) / DRAG_FRACTION.1; self.racer.vy = (self.racer.vy * DRAG_FRACTION.0) / DRAG_FRACTION.1;
self.racer.vx += instruction.vx as SpeedType; self.racer.vx += instruction.vx;
self.racer.vy += instruction.vy as SpeedType; self.racer.vy += instruction.vy;
self.racer.x += self.racer.vx as PosType; self.racer.x += self.racer.vx as isize;
self.racer.y += self.racer.vy as PosType; self.racer.y += self.racer.vy as isize;
} }
fn push_from_asteroids(&mut self) -> bool { fn push_from_asteroids(&mut self) -> bool {
@ -413,9 +402,9 @@ impl Simulation {
let racer_parts = parts_fn(); let racer_parts = parts_fn();
let racer = Racer { let racer = Racer {
x: racer_parts[0].parse::<PosType>().unwrap(), x: racer_parts[0].parse::<isize>().unwrap(),
y: racer_parts[1].parse::<PosType>().unwrap(), y: racer_parts[1].parse::<isize>().unwrap(),
radius: racer_parts[2].parse::<SizeType>().unwrap(), radius: racer_parts[2].parse::<isize>().unwrap(),
vx: 0, vx: 0,
vy: 0, vy: 0,
}; };
@ -423,10 +412,10 @@ impl Simulation {
let bb_parts = parts_fn(); let bb_parts = parts_fn();
let bbox = BoundingBox { let bbox = BoundingBox {
min_x: bb_parts[0].parse::<SizeType>().unwrap(), min_x: bb_parts[0].parse::<isize>().unwrap(),
min_y: bb_parts[1].parse::<SizeType>().unwrap(), min_y: bb_parts[1].parse::<isize>().unwrap(),
max_x: bb_parts[2].parse::<SizeType>().unwrap(), max_x: bb_parts[2].parse::<isize>().unwrap(),
max_y: bb_parts[3].parse::<SizeType>().unwrap(), max_y: bb_parts[3].parse::<isize>().unwrap(),
}; };
let asteroid_count = parts_fn()[0].parse::<usize>().unwrap(); let asteroid_count = parts_fn()[0].parse::<usize>().unwrap();
@ -436,9 +425,9 @@ impl Simulation {
let asteroid_parts = parts_fn(); let asteroid_parts = parts_fn();
asteroids.push(Asteroid { asteroids.push(Asteroid {
x: asteroid_parts[0].parse::<PosType>().unwrap(), x: asteroid_parts[0].parse::<isize>().unwrap(),
y: asteroid_parts[1].parse::<PosType>().unwrap(), y: asteroid_parts[1].parse::<isize>().unwrap(),
radius: asteroid_parts[2].parse::<PosType>().unwrap(), radius: asteroid_parts[2].parse::<isize>().unwrap(),
}); });
} }
@ -449,9 +438,9 @@ impl Simulation {
let goal_parts = parts_fn(); let goal_parts = parts_fn();
goals.push(Asteroid { goals.push(Asteroid {
x: goal_parts[0].parse::<PosType>().unwrap(), x: goal_parts[0].parse::<isize>().unwrap(),
y: goal_parts[1].parse::<PosType>().unwrap(), y: goal_parts[1].parse::<isize>().unwrap(),
radius: goal_parts[2].parse::<PosType>().unwrap(), radius: goal_parts[2].parse::<isize>().unwrap(),
}); });
} }

View file

@ -7,7 +7,7 @@ use std::path::PathBuf;
mod asteracer; mod asteracer;
pub fn load_asteroid_graph(path: &PathBuf) -> ( pub fn load_asteroid_graph(path: &PathBuf) -> (
Vec<(PosType, PosType)>, Vec<(isize, isize)>,
Vec<(usize, usize)>, Vec<(usize, usize)>,
Vec<(char, usize)>, Vec<(char, usize)>,
) { ) {
@ -37,7 +37,7 @@ pub fn load_asteroid_graph(path: &PathBuf) -> (
// Load vertices // Load vertices
for i in 0..(n_racer + n_asteroid + n_goal) { for i in 0..(n_racer + n_asteroid + n_goal) {
let line: Vec<i64> = iter let line: Vec<isize> = iter
.next() .next()
.unwrap() .unwrap()
.split_whitespace() .split_whitespace()
@ -120,7 +120,7 @@ fn main() {
// posbíráme zbývající cíle tak, že k nim poletíme přímou čarou // posbíráme zbývající cíle tak, že k nim poletíme přímou čarou
while simulation.reached_goals.iter().any(|&reached| !reached) { while simulation.reached_goals.iter().any(|&reached| !reached) {
let mut nearest_goal = None; let mut nearest_goal = None;
let mut nearest_goal_distance = PosType::MAX; let mut nearest_goal_distance = isize::MAX;
for (i, &reached) in simulation.reached_goals.iter().enumerate() { for (i, &reached) in simulation.reached_goals.iter().enumerate() {
if !reached { if !reached {