diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1975872 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +# x86life: a life simulating game +# + +PROGRAM_NAME=x86life +ASM=nasm + +.PHONY: clean + +$(PROGRAM_NAME): src/life.asm src/commonio_read.asm src/commonio_write.asm + $(ASM) -f elf64 -g -F dwarf -o $(PROGRAM_NAME).o src/life.asm + $(LD) $(PROGRAM_NAME).o -o $(PROGRAM_NAME) + +clean: + rm -fv $(PROGRAM_NAME) $(PROGRAM_NAME).o diff --git a/src/commonio_read.asm b/src/commonio_read.asm new file mode 100644 index 0000000..91d3112 --- /dev/null +++ b/src/commonio_read.asm @@ -0,0 +1,35 @@ +; x86life: a life simulating game +; Copyright (C) 2023-2024 luca0N! +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, version 3 of the License. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +; +; Contact: + +.read: + ; read digit stdin + XOR RAX, RAX + XOR RDI, RDI + SYSCALL + + ; remove line feed, if present + DEC RAX + ADD RSI, RAX + CMP [RSI], BYTE 0x0A + JNE .read_ret + MOV [RSI], BYTE 0x00 +.read_ret: + SUB RSI, RAX + RET + +; vim: syntax=nasm ts=4 sw=4 et diff --git a/src/commonio_write.asm b/src/commonio_write.asm new file mode 100644 index 0000000..68475df --- /dev/null +++ b/src/commonio_write.asm @@ -0,0 +1,36 @@ +; x86life: a life simulating game +; Copyright (C) 2023-2024 luca0N! +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, version 3 of the License. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +; +; Contact: + +.print: + CALL .print_getLen +.printLen: + MOV RAX, 1 + MOV RDI, 1 + SYSCALL + RET + +.print_getLen: + MOV RDX, RSI +.print_getLen_loop: + INC RDX + CMP [RDX], BYTE 0x00 + JNE .print_getLen_loop + SUB RDX, RSI + RET + +; vim: syntax=nasm ts=4 sw=4 et diff --git a/src/life.asm b/src/life.asm new file mode 100644 index 0000000..fbc3b3e --- /dev/null +++ b/src/life.asm @@ -0,0 +1,158 @@ +; x86life: a life simulating game +; Copyright (C) 2023-2024 luca0N! +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, version 3 of the License. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +; +; Contact: + +%include "src/commonio_write.asm" +%include "src/commonio_read.asm" +%include "src/printno.asm" + +%define SYS_EXIT 60 +%DEFINE PROTAGONIST_NAME_LEN 31 +%DEFINE PROT_BUF_OFFSET_AGE 31 + +; The maximum age of the protagonist. Since we're using 7 bits for the age, +; this needs to be 127 (7Fh). +%DEFINE PROT_AGE_MAX 7Fh + +SECTION .data +buf_protagonist: ; protagonist buffer. 31 bytes for + TIMES 32 DB 0 ; the name, 1 for age +buf_in: ; stdin buffer + TIMES 32 DB 0 +buf_age: + TIMES 3 DB 0 + +SECTION .rodata +str_newln: + DB 0x0A, 0x00 +str_intro: + DB "Welcome to Life!", 0x0A, "Insert the name of the ", \ + "protagonist: ", 0x00 +str_intro_name_invalid: + DB "The protagonist name must be within 1-32 boundaries.", \ + 0x0A, 0x00 +str_game_dead: + DB "The protagonist has died at an age of: ", 0x00 + +str_birth: + DB "The protagonist was born today!", 0x0A, 0x00 +str_birthday_1: + DB "Today, ", 0x00 +str_birthday_2: + DB " turns ", 0x00 +str_birthday_3: + DB "!", 0x0A, 0x00 +str_continue: + DB "Press any key to age. ", 0x00 + +SECTION .text + GLOBAL _start +_start: + MOV RSI, str_intro ; print intro msg + CALL .print + MOV RSI, buf_protagonist ; read to buffer from stdin + MOV RDX, PROT_BUF_OFFSET_AGE + CALL .read + PUSH RAX + CMP RAX, 32 ; check whether the user send more + ; data than the buffer could hold + JE .invalidNameBounds + + POP RAX + CMP RAX, 2 + JLE .invalidNameBounds + + ; Begin the game!! + MOV RSI, str_birth + CALL .print + +.begin: + ; Check whether the protagonist is still alive + ; ============================================ + ; Alive/dead status is represented by the most + ; significant bit in the age byte + MOV RAX, [buf_protagonist + PROT_BUF_OFFSET_AGE] + AND RAX, 80h + CMP RAX, 80h + JE .protDead + + ; Print age header + ; ================ + MOV RSI, str_birthday_1 ; "Today, " + CALL .print + MOV RSI, buf_protagonist ; (protagonist) + CALL .print + MOV RSI, str_birthday_2 ; " turns " + CALL .print + + MOV RSI, buf_age ; (age) + MOV RAX, [buf_protagonist + PROT_BUF_OFFSET_AGE] + CALL .getHumanReadableNum ; (n) + MOV RSI, buf_age + MOV RDX, 3 + CALL .printLen + + MOV RSI, str_birthday_3 ; (!) + CALL .print + + MOV RSI, str_continue + CALL .print + LEA RSI, [buf_protagonist + PROT_BUF_OFFSET_AGE] + MOV RAX, [RSI] + INC RAX + CMP RAX, PROT_AGE_MAX + JNE .begin_continue + OR RAX, 80h ; kill protagonist +.begin_continue: + MOV [RSI], RAX + MOV RSI, buf_in + MOV RDX, 1 + CALL .read + + JMP .begin +.protDead: + ; Subroutine: the protagonist has died + ; ==================================== + MOV RSI, str_game_dead + CALL .print + MOV RSI, buf_age ; (age) + MOV RAX, [buf_protagonist + PROT_BUF_OFFSET_AGE] + AND RAX, 7Fh ; get rid of life bit + CALL .getHumanReadableNum + MOV RSI, buf_age + MOV RDX, 3 + CALL .printLen + MOV RSI, str_newln + CALL .print + JMP .exit + +.invalidNameBounds: +; MOV RSI, buf_protagonist +; CALL .read ; keep reading until there is nothing +; CMP RAX, 0 ; more to read to prevent pending data +; JNE .nameTooBig ; from going to the buffer + + MOV RSI, str_intro_name_invalid + CALL .print + JMP _start + +.exit: + MOV RAX, SYS_EXIT + XOR RDI, RDI + SYSCALL + +; vim: syntax=nasm ts=4 sw=4 et diff --git a/src/printno.asm b/src/printno.asm new file mode 100644 index 0000000..b92d856 --- /dev/null +++ b/src/printno.asm @@ -0,0 +1,63 @@ +; x86life: a life simulating game +; Copyright (C) 2023-2024 luca0N! +; +; This program is free software: you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, version 3 of the License. +; +; This program is distributed in the hope that it will be useful, +; but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . +; +; +; Contact: + +;SECTION .rodata +; num equ 20 +;SECTION .data +; buf times 3 db 20h +;SECTION .text +; GLOBAL _start +.getHumanReadableNum: +; MOV AL, num ; put num on RAX + PUSH RAX ; save num on stack + MOV BL, 100 ; check for hundredth digit + DIV BL ; divide RAX by BL + CMP AL, 0 + +; MOV RSI, buf + ADD AL, 0x30 ; digit to ASCII + MOV [RSI], AL + + SUB AL, 0x30 + MUL BL ; multiply AL by BL + MOV BL, AL + POP RAX ; get original saved number + SUB AL, BL ; discard hundreds + + PUSH RAX + MOV BL, 10 + DIV BL ; get tenth digit + + INC RSI ; next buffer byte + ADD AL, 0x30 ; digit to ASCII + MOV [RSI], AL + + SUB AL, 0x30 + MUL BL + MOV BL, AL + POP RAX + SUB AL, BL + + INC RSI + ADD AL, 0x30 + MOV [RSI], AL + + SUB RSI, 2 + RET + +; vim: syntax=nasm ts=4 sw=4 et