crankstart 0.1.2

A barely functional, wildly incomplete and basically undocumented Rust crate whose aim is to let you write games for the [Playdate handheld gaming system](https://play.date) in [Rust](https://www.rust-lang.org).
Documentation
#![no_std]

extern crate alloc;

use {
    alloc::boxed::Box,
    anyhow::Error,
    crankstart::{
        crankstart_game,
        graphics::{Graphics, LCD_COLUMNS, LCD_ROWS, LCD_ROWSIZE},
        system::{PDButtons, System},
        Game, Playdate,
    },
    randomize::PCG32,
};

const LIMIT: usize = (LCD_COLUMNS - 1) as usize;

fn ison(row: &'static [u8], x: usize) -> bool {
    (row[x / 8] & (0x80 >> (x % 8))) == 0
}

fn val(row: &'static [u8], x: usize) -> u8 {
    1 - ((row[x / 8] >> (7 - (x % 8))) & 1)
}

fn rowsum(row: &'static [u8], x: usize) -> u8 {
    if x == 0 {
        val(row, LIMIT) + val(row, x) + val(row, x + 1)
    } else if x < LIMIT {
        return val(row, x - 1) + val(row, x) + val(row, x + 1);
    } else {
        val(row, x - 1) + val(row, x) + val(row, 0)
    }
}

fn middlerowsum(row: &'static [u8], x: usize) -> u8 {
    if x == 0 {
        val(row, LIMIT) + val(row, x + 1)
    } else if x < LIMIT {
        val(row, x - 1) + val(row, x + 1)
    } else {
        val(row, x - 1) + val(row, 0)
    }
}

fn do_row<'a>(
    lastrow: &'static [u8],
    row: &'static [u8],
    nextrow: &'static [u8],
    outrow: &'a mut [u8],
) {
    let mut b = 0;
    let mut bitpos = 0x80;

    for x in 0..(LCD_COLUMNS as usize) {
        // If total is 3 cell is alive
        // If total is 4, no change
        // Else, cell is dead

        let sum = rowsum(lastrow, x) + middlerowsum(row, x) + rowsum(nextrow, x);

        if sum == 3 || (ison(row, x) && sum == 2) {
            b |= bitpos;
        }

        bitpos >>= 1;

        if bitpos == 0 {
            outrow[x / 8] = !b;
            b = 0;
            bitpos = 0x80;
        }
    }
}

fn randomize(graphics: &Graphics, rng: &mut PCG32) -> Result<(), Error> {
    let frame = graphics.get_display_frame()?;
    let start = 0;
    for element in &mut frame[start..] {
        *element = rng.next_u32() as u8;
    }
    Ok(())
}

struct Life {
    rng: PCG32,
    started: bool,
}

const LAST_ROW_INDEX: usize = ((LCD_ROWS - 1) * LCD_ROWSIZE) as usize;
const LAST_ROW_LIMIT: usize = LAST_ROW_INDEX + LCD_ROWSIZE as usize;

impl Life {
    pub fn new(_playdate: &Playdate) -> Result<Box<Self>, Error> {
        let rng0 = PCG32::seed(1, 1);
        Ok(Box::new(Self {
            rng: rng0,
            started: false,
        }))
    }
}

impl Game for Life {
    fn update(&mut self, _playdate: &mut Playdate) -> Result<(), Error> {
        let graphics = Graphics::get();
        if !self.started {
            randomize(&graphics, &mut self.rng)?;
            self.started = true;
        }

        let (_, pushed, _) = System::get().get_button_state()?;

        if (pushed & PDButtons::kButtonA) == PDButtons::kButtonA {
            randomize(&graphics, &mut self.rng)?;
        }

        let frame = graphics.get_frame()?;

        let last_frame = graphics.get_display_frame()?;
        let mut last_row = &last_frame[LAST_ROW_INDEX..LAST_ROW_LIMIT];
        let mut row = &last_frame[0..LCD_ROWSIZE as usize];
        let mut next_row = &last_frame[LCD_ROWSIZE as usize..(LCD_ROWSIZE * 2) as usize];
        for y in 0..LCD_ROWS {
            let index = (y * LCD_ROWSIZE) as usize;
            let limit = index + LCD_ROWSIZE as usize;
            do_row(last_row, row, next_row, &mut frame[index..limit]);

            last_row = row;
            row = next_row;
            let next_row_index = (y + 2) % LCD_ROWS;

            let index = (next_row_index * LCD_ROWSIZE) as usize;
            let limit = index + LCD_ROWSIZE as usize;
            next_row = &last_frame[index..limit];
        }

        graphics.mark_updated_rows(0..=(LCD_ROWS as i32) - 1)?;

        Ok(())
    }
}

crankstart_game!(Life);