Вход

Просмотр полной версии : sPycialist - эмулятор ПК Специалист на Python



CityAceE
26.12.2018, 07:45
Что это

sPycialist - это эмулятор ПК Специалист, написанный с чистого листа и полностью на языке программирования Python.

История появления

Вообще, изначально вдохновившись эмулятором ZX Spectrum - PyZX, также написанном на Python, я собирался написать свой эмулятор, так как PyZX по большей части - это автоматически конвертированный с Java эмулятор Jasper. Из-за этого его исходный код очень кривой и некрасивый. И вот я начал писать ZX-Pylot. Сделал эмуляцию первых 256 команд и подумал, что по сути у меня готова эмуляция процессора Intel 8080 (КР580ВМ80А) и вспомнил свой первый компьютер "Специалист", который построен на этом процессоре. И в результате вместо того, чтобы писать ZX-Pylot, я переключился на эмулятор Специалиста - sPycialist.

Ключевые особенности


100% Python
Из зависимостей используется только библиотеки pygame и numpy
Полная эмуляция процессора Intel 8080 (КР580ВМ80А), включая все флаги
Поддержка загрузки игр в RKS-формате

Использование

Эмулятор запускается из командной строки:


python spycialist.py

Чтобы сменить игру, необходимо положить игру в формате RKS рядом с файлами эмулятора. Далее требуется изменить имя файла в константе GAME в файле spycialist.py. После чего потребуется перезапустить эмулятор.

Информация для тех, кто не знаком с ПК Специалист: Чтобы запустить загруженную игру, после запуска эмулятора нужно нажать следующую последовательность клавиш: <F1>, <ENTER>, <U>, <ENTER>.

Переназначить клавиши на свой вкус можно в файле spyc_keyboard.py. По умолчанию используется раскладка JCUKENG, как наиболее приближенная к оригиналу.

Для чего это

Данный эмулятор может представлять лишь академический интерес из-за невысокой скорости эмуляции и отсутствия какого-либо сервиса. Эмулятор писался сугубо из личного интереса. Тем не менее, основу sPycialist составляет эмулятор Intel 8080. Эмуляция данного процессора получилась достаточно точной - полностью проходятся все тесты 8080/8085 CPU Exerciser. Реализация Intel 8080 находится в отдельном файле и почти никак не завязана на эмуляцию ПК Специалст. Таким образом данный эмулятор процессора Intel 8080 может быть с успехом использован в каких-то сторонних Python-проектах.

Известные проблемы


Скорость эмуляции оставляет желать лучшего, так как Python - это не тот язык, на котором пишут эмуляторы
Нет эмуляции звука

Где взять

Проект расположен на GitHub (https://github.com/CityAceE/sPycialist), а также для удобства приложен к этому сообщению.

История версий

v0.5 (20 января 2019 г.)
- Дальнейшая оптимизация кода: на быстрых компьютерах доступна 100%-ная скорость эмуляции
- Оптимизация опроса клавиатуры: устранено кросс-нажатие клавиш

v0.4 (2 января 2019 г.)
- Дальнейшая оптимизация кода
- Новая процедура вывода на экран (svofski)
- Новая процедура опроса клавиатуры (ivagor)

v0.3 (31 декабря 2018 г.)
- Существенное увеличение скорости эмуляции за счёт оптимизации вывода экрана

v0.2 (29 декабря 2018 г.)
- Оптимизация кода для повышения быстродействия

v0.1 (26 декабря 2018 г.)
- Первый релиз



Скриншоты

https://pic.maxiol.com/images/1545797292.3254906935.i8080validator2.png (https://pic.maxiol.com/images/1545797292.3254906935.i8080validator2.png)https://pic.maxiol.com/images/1545797039.3254906935.i8080validator3.png
https://pic.maxiol.com/images/1546225871.90463878.zoo.gif https://pic.maxiol.com/images/1546247169.90463878.gold.gif
https://pic.maxiol.com/images/1545567716.90463878.chess.png https://pic.maxiol.com/images/1545798471.3254906935.chessbasic.png

Q-Master
26.12.2018, 22:11
Сразу тебе советую заюзать для регистров memoryview, что-то вроде:

_HL_b = bytearray(2)
_H_L = memoryview(_HL_b)
_H = _H_L[0:1]
_L = _H_L[1:2]
_HL = _H_L.cast('H')
Доступ к половинкам и целым регистрам станет очень сильно быстрее и лежать они будут в одном физическом месте в памяти.
Доступ будет как к массиву через _L[0] или _HL[0]
Плюс замени все операции маскирования по FF на остаток от деления на маска + 1.
Память лучше тоже через memoryview:

mem = memoryview(bytearray(65536))

CityAceE
27.12.2018, 00:34
Q-Master, спасибо за столь ценные советы. Никогда до этого не приходилось сталкивался с memoryview! Есть отличный повод разобраться и применить. Авось эмулятор начнёт шевелиться чуть шустрее :)

hobot
27.12.2018, 03:41
CityAceE, скриншоты просто прослезили ) Создал тему эмулятора на игровом форуме, замечательный предпраздничный релиз!
https://www.old-games.ru/forum/posts/1521263/

Мне вот что подумалось, сколько в IT отрасли ещё специалистов - у которых первой машиной была отечественная микроЭВМ? Может опрос какой-то организовать? Естественно все клоны спектрумов (и т.п.) нашего производства идут в зачёт как отечественные (что естественно!)

Titus
27.12.2018, 05:31
Авось эмулятор начнёт шевелиться чуть шустрее
Я бы на твоем месте еще сделал работу с флагами не так, как у тебя - каждый флаг - отдельная переменная. А держал бы все их в одном байте или слове. Ну и отказался бы от универсальности эмуляции 8080/Z80.

CityAceE
27.12.2018, 09:00
Я бы на твоем месте еще сделал работу с флагами не так, как у тебя - каждый флаг - отдельная переменная. А держал бы все их в одном байте или слове.
А какие это может дать преимущества? Как я себе это понимаю, когда каждый флаг в отдельной переменной, его можно быстрее устанавливать/сбрасывать. Всего пара команд i8080 требует полного флагового регистра, и для этих команд не сильного дорого запаковать все флаги в один байт и производить обратную процедуру. А все остальные команды расставляют флаги по отдельности.


Ну и отказался бы от универсальности эмуляции 8080/Z80.
Это, конечно, не сильно скажется на производительности, но, да, отказаться можно.

svofski
27.12.2018, 09:27
Присоединюсь к хору советчиков =)

1) Память списком это роскошно. Лучше представить ее байтовым массивом.
2) Ассоциативная таблица, в которой ключ - байт и каждому ключу чего-то соответствует, это просто массив. Можно оставить таблицу для инициализации, потому что так ее проще читать, но для исполнения лучше перенести все хандлеры инструкций в простой линейный список.
3) Счетчик тактов лучше сразу делать как m-state/t-state (например, [5,3,3]). Это пригодится для эмуляторов, где надо считать такты с учетом вейтов, хода луча итд.
4) Я бы конечно сделал процессор классом. Сейчас и так у тебя все пронизано globals. А с классом это будут члены класса и ты будешь на них ссылаться как self.flag_c итд.

CityAceE
27.12.2018, 10:33
svofski, спасибо за то, что нашёл время и интерес заглянуть в текст эмулятора и высказать свои соображения.


1) Память списком это роскошно. Лучше представить ее байтовым массивом.
Уже сделал по рекомендации Q-Master'а.


2) Ассоциативная таблица, в которой ключ - байт и каждому ключу чего-то соответствует, это просто массив. Можно оставить таблицу для инициализации, потому что так ее проще читать, но для исполнения лучше перенести все хандлеры инструкций в простой линейный список.
Не до конца понял мысль. В чём профит? Так мне было и будет проще менять процедуры обработки на тот случай, если вдруг я захочу что-то объединить, или наоборот выделить обработку какую-то команды в отдельную процедуру. Визуально это выглядит яснее. Хотя опять же, может быть я просто не до конца понял твою мысль.


3) Счетчик тактов лучше сразу делать как m-state/t-state (например, [5,3,3]). Это пригодится для эмуляторов, где надо считать такты с учетом вейтов, хода луча итд.
Я подумаю над этим. Но уж больно утопичной выглядит идея написания на Python эмулятора, который будет учитывать ход луча :) У меня в эмуляторе нет ни одной задержки, так как задерживать нечего - всё и так слишком медленно шевелится.


4) Я бы конечно сделал процессор классом. Сейчас и так у тебя все пронизано globals. А с классом это будут члены класса и ты будешь на них ссылаться как self.flag_c итд.
Да, тоже уже думал над этим, когда начал плодить global'ы. Но в итоге доделал уже так. Хотя мне эта масса global'ов совсем не нравится.

Сейчас пытаюсь перевести хранение регистров и пока плохо получается. Нужно было всё делать разом сразу и везде. И я вроде сделал, но у меня теперь всё сломалось. Пытаюсь понять что и где пошло не так. Но это не так просто.

svofski
27.12.2018, 12:44
CityAceE, в ассоциативной таблице выборка по ключу это поиск в хеш таблице. Операция O(1) формально, но на практике довольно сложная. Выборка из линейного массива это просто вычисление смещения. В Питоне может быть и невелика будет разница, но я бы попробовал.

Titus
27.12.2018, 13:46
А какие это может дать преимущества? Как я себе это понимаю, когда каждый флаг в отдельной переменной, его можно быстрее устанавливать/сбрасывать. Всего пара команд i8080 требует полного флагового регистра, и для этих команд не сильного дорого запаковать все флаги в один байт и производить обратную процедуру. А все остальные команды расставляют флаги по отдельности.

Я не знаю, питона, поэтому пример приведу на Си:

Вот, например, твой вариант RLCA:


reg_a = reg_a << 1
flag_c = bool(reg_a & 0x100)
reg_a = (reg_a | flag_c) & 0xff
if not i8080:
flag_h = False
fflag_n(False)
fflag_3(bool(reg_a & 0b00001000))
fflag_5(bool(reg_a & 0b00100000))


И как можно оптимально это эмулировать (без учета универсальности Z80/8080):


reg_A = (reg_A << 1) | (reg_A >> 7);
reg_F = (reg_A & CONST_53C) | (reg_F & CONST_SZPH);

CityAceE
27.12.2018, 15:32
Я не знаю, питона, поэтому пример приведу на Си:
Приведённый тобой пример будет работать в Python без каких-либо изменений, даже точки с запятой убирать не придётся :) Спасибо, я поизучаю и такой вариант, попробую померить скорость и если будет выигрыш, то применю.


В Питоне может быть и невелика будет разница, но я бы попробовал.
Тоже нужно будет прикинуть и измерить скорость обоих вариантов. Хотя в Python я ещё ни разу не использовал вызов функции по значению из списка - пользовался только вызовом по словарю, как в этом эмуляторе. И даже не знаю осуществимо ли это? Видимо, осуществимо.

Я уже получил довольно много полезных советов, обязательно воспользуюсь ими всеми. Мне бы сейчас заставить работать систему при варианте хранения регистров в memoryview. Мало того, что оно просто сейчас перестало работать, так ещё и всякие нюансы всплыли. Например, вариант, предложенный Titus'ом:



reg_A = (reg_A << 1) | (reg_A >> 7);


при таком методе хранения работать не будет, например, если reg_A == 0xFF. Как только сдвинутся биты внутри первых скобок я получу ошибку, что это значение не может хранится в байте. Вот и приходится огород городить из временных переменных, что явно не идёт на руку скорости эмуляции.

Titus
27.12.2018, 15:39
Приведённый тобой пример будет работать в Python без каких-либо изменений, даже точки с запятой убирать не придётся :) Спасибо, я поизучаю такой вариант, попробую померить скорость и если будет выигрыш, то применю.

Замечу, что reg_A должен быть формата uint8, иначе придется сделать иначе.

Это фрагмент из моего эмулятора Z80. Он очень быстрый. Ну очень) Куда уж быстрее)

svofski
27.12.2018, 16:38
Хотя в Python я ещё ни разу не использовал вызов функции по значению из списка - пользовался только вызовом по словарю, как в этом эмуляторе. И даже не знаю осуществимо ли это? Видимо, осуществимо.
Конечно осуществимо. И из списка и из тупли.

Написал незамысловатый бенчмарк. С современными компами и интерпретируемыми языками уровня питона такие бенчмарки мало толку дают. У меня получается разброс в 10% между запусками, слишком много внешних воздействий. В среднем, как мне показалось, тупля незначительно опережает список, список незначительно опережает словарь, но в общем это ловля блох и вряд ли эмулятор ускорится в два раза от такой переделки. Хотя в реальном коде разница может оказаться другой и, учитывая то, что попробовать вообще ничего не стоит, по-моему стоит попробовать ;)



import time

def foo(): return 'foo'
def bar(): return 'bar'

disp=[foo if i & 1 else bar for i in range(256)]
dict={}
for i in range(256):
dict[i] = foo if i & 1 else bar


niter = 100000000

print("dispatch using list")
start_time = time.time()
for i in range(niter):
u = disp[i & 255]()
elapsed_time = time.time() - start_time
print("elapsed time=", elapsed_time)

print("dispatch using dictionary")
start_time = time.time()
for i in range(niter):
u = dict[i & 255]()
elapsed_time = time.time() - start_time
print("elapsed time=", elapsed_time)

tust=tuple(disp)
print("dispatch using tuple")
start_time = time.time()
for i in range(niter):
u = tust[i & 255]()
elapsed_time = time.time() - start_time
print("elapsed time=", elapsed_time)


- - - Добавлено - - -

P.S. заметил, что вставил версию без вызова функции, только с выборкой. Перепроверил, разницы нет, но на всякий случай -- в замеряемых циклах добавить ().

Q-Master
27.12.2018, 22:13
CityAceE, в ассоциативной таблице выборка по ключу это поиск в хеш таблице. Операция O(1) формально, но на практике довольно сложная. Выборка из линейного массива это просто вычисление смещения. В Питоне может быть и невелика будет разница, но я бы попробовал.

Нет. Это не так. Для ключа типа int это поиск ровно такой-же как и смещение в массиве. В питон3.6 и выше это поведение еще более оптимизировали.
CityAceE: Оставь диктом. Флаги у тебя тоже верно сделаны, т.к. смысла тащить их везде в виде битмаски абсолютно не нужно. Титус прав насчет расчетов, но ошибается насчет переносимости. В питон битовые операции будут сильно медленнее чем обычная математика. Если уж на то пошло, то можно использовать какую-нибудь библиотеку. Тот-же rlca переписаный слегка из pyzx:


def rlca():
global _f3, _f5, _fN, _fH, _fC
ans = _A[0]
c = (ans & 0x80) != 0
ans = (((ans << 1) | 0x01) if c else (ans << 1)) % 256
_f3 = ((ans & F_3) != 0)
_f5 = ((ans & F_5) != 0)
_fN = False
_fH = False
_fC = c
_A[0] = ans
return 4


- - - Добавлено - - -

Написал тестик:


from random import randint
import timeit
def foo(): return 'foo'
def bar(): return 'bar'
disp=[foo if i & 1 else bar for i in range(256)]
def test(): u = disp[randint(0, 255)]()
timeit.timeit(test, number=100000000)

2,511099100112915 сек.



from random import randint
import timeit
def foo(): return 'foo'
def bar(): return 'bar'
disp={i: (foo if i % 2 else bar) for i in range(256)}
def test(): u = disp[randint(0, 255)]()
timeit.timeit(test, number=100000000)

2,5191421508789062 сек.

Весьма показательно. 0.3% разницы на 1 миллионе вызовов.

svofski
28.12.2018, 00:04
Нет. Это не так. Для ключа типа int это поиск ровно такой-же как и смещение в массиве. В питон3.6 и выше это поведение еще более оптимизировали.
Питон трудный язык ;)

У меня не получилось запустить тесты в таком виде. Я добавил print() и уменьшил на порядок число итераций:


from random import randint
import timeit
def foo(): return 'foo'
def bar(): return 'bar'
disp=[foo if i & 1 else bar for i in range(256)]
def test(): u = disp[randint(0, 255)]()
print("list...")
print(timeit.timeit(test, number=10000000))

disp={i: (foo if i % 2 else bar) for i in range(256)}
def test(): u = disp[randint(0, 255)]()
print("dict...")
print(timeit.timeit(test, number=10000000))


И хотя это не принципиально, но все же вопрос:


$ python3 testqmaster.py
list...
11.997851000051014
dict...
11.946656999993138

Это i7 8550U, python 3.6.5, WSL. Что же за камень делает в 10 раз больше за 2.5 сек?

Q-Master
28.12.2018, 21:25
Питон трудный язык ;)

У меня не получилось запустить тесты в таком виде. Я добавил print() и уменьшил на порядок число итераций:


from random import randint
import timeit
def foo(): return 'foo'
def bar(): return 'bar'
disp=[foo if i & 1 else bar for i in range(256)]
def test(): u = disp[randint(0, 255)]()
print("list...")
print(timeit.timeit(test, number=10000000))

disp={i: (foo if i % 2 else bar) for i in range(256)}
def test(): u = disp[randint(0, 255)]()
print("dict...")
print(timeit.timeit(test, number=10000000))


И хотя это не принципиально, но все же вопрос:


$ python3 testqmaster.py
list...
11.997851000051014
dict...
11.946656999993138

Это i7 8550U, python 3.6.5, WSL. Что же за камень делает в 10 раз больше за 2.5 сек?

Я косяк с копипастой. :) Там 1000000 итераций, как оно скопипастилось как 100000000 - фиг знает + это было на 2.7, а вот на 3.7 с массивом - 3.174003737999996, с диктом - 3.1171019020000017, что показывает что дикт в 3.7 даже еще больше оптимизирован, чем я думал

svofski
28.12.2018, 22:33
Q-Master, понятно. Все это немного удивительно, потому что интуитивно все-таки даже очень простой хеш код это все равно лишняя операция, и в бакете может быть больше одного элемента. А индекс в массиве -- это просто индекс. Но понятно, что в Питоне все на самом деле не так просто.

Добавил еще такой же тест с туплей, гонял много раз и не заметил никакого статистически значимого перевеса ни в одну из сторон. С одной стороны и хорошо, а с другой обидно, что не проявить хитринку. Еще значимый момент на лаптопе -- троттлить начинает тут же, поэтому первый прогон дает совсем перекошенный результат, который надо выкидывать.

list...
12.346341000054963
tuple...
12.456940999953076
dict...
12.440088999923319

list...
12.06766299996525
tuple...
12.148910999996588
dict...
12.198226999957114

list...
12.217213000054471
tuple...
12.13867199991364
dict...
12.269977999967523

CityAceE
29.12.2018, 08:04
Провёл оптимизацию по рекомендациям из этой ветки:

- Перевёл хранение регистров и памяти в memoryview, переделал эмуляцию всех инструкций под такой формат
- Заменил все операции & 0xF, & 0xFF и & 0xFFFF на более быстрые операции получения остатка от деления
- Отказался от универсальной эмуляции i8080/Z80 и, соответственно от лишних проверок и флагов

По списку вроде бы не много, но пришлось перелопачивать почти всё, а потом ещё долго искать почему перестало работать.

Не стал менять хранение флагов, а также не стал переносить процессор в класс.

Новая версия в первом посте.

Всем спасибо за советы и рекомендации. Посмотрите текст скрипта, вдруг что-то ему можно улучшить и оптимизировать?

Результат, конечно, есть, но он не слишком впечатляет (слева - v0.1, справа - v0.2):

https://pic.maxiol.com/images/1546059834.3254906935.zoo01.gif https://pic.maxiol.com/images/1546059859.3254906935.zoo02.gif

CityAceE
31.12.2018, 05:40
Изменил логику обновления экрана. Результат порадовал!

Слева - v0.2, справа - неопубликованная версия с оптимизацией вывода экрана:
https://pic.maxiol.com/images/1546059859.3254906935.zoo02.gif https://pic.maxiol.com/images/1546225871.90463878.zoo.gif

Ну, и для сравнения реальная скорость игры в Emu80:

https://pic.maxiol.com/images/1546224855.90463878.zooemu80.gif

Ещё бы чуть-чуть и скорость стала бы реальной, но у меня закончились мысли, что можно ещё сделать, чтобы ускорить эмулятор.

svofski
31.12.2018, 10:47
Ещё бы чуть-чуть и скорость стала бы реальной, но у меня закончились мысли, что можно ещё сделать, чтобы ускорить эмулятор.

Так публикуй последние изменения, а то не видно что оптимизировать.

CityAceE
31.12.2018, 12:23
Опубликовал v0.3 в первом посте и на GitHub (https://github.com/CityAceE/sPycialist).

Но до сих пор из-за упрощённой эмуляции К580ВВ55А не работает управление в играх, которые для опроса клавиатуры используют собственные процедуры, а не пользуются стандартной подпрограммой "Загрузчика". Пока я нашёл три таких программы:

Тест С.Рюмика
Игра Gold
Игра Jet-Set Willy

Наверняка есть и другие игры.

Я делал эмуляцию клавиатуры на основании анализа работы "Загрузчика". Свои изыскания опубликовал (https://zx-pk.ru/entries/293-klaviatura-pk-spetsialist-s-tochki-zreniya-programmista.html) у себя в дневнике. Однако другие программы используют какие-то нюансы работы ВВ55А. Эти программы записывают байты в порты (не в регистр управления!), а потом считывают данные с соседнего порта и на основании того, что считалось, делают вывод о том нажата кнопка или нет. Закономерность того, что и в каких случаях считывается я так и не смог понять. Может быть кто-то сможет разъяснить?

Вот, например, тест С.Рюмика в режиме работы порта #82 (канал B на считывание), последовательно засылает в канал С значения 0E, 0D, 0B. Если нажата кнопка F (левая верхняя на клавиатуре Специалиста), то после засылки этих значений из канала B считывается FF. Но если подать в канал С значение 7, то из канала B считывается 7F - сбрасывается старший бит, который как раз соответствует столбцу клавиши F на клавиатуре. Почему так происходит я понять не могу. В моём эмуляторе сейчас всегда будет считываться 7F, если нажата клавиша F, какие бы байты не засылали в соседние каналы.

svofski
31.12.2018, 12:47
Все просто: $80 & CW: bit set/reset mode. Bit 0: 1=set, 0=reset, bit 1-3=bit number.

CityAceE
31.12.2018, 13:07
svofski, спасибо. Но можно для тупых более подробно?

ivagor
31.12.2018, 13:18
Не мне бы конечно писать на эту тему, но пока настоящие специалисты не подтянулись - можно посмотреть описание Pyk (http://emu80.org/dev/dev_s.html), фрагмент со слов "Для опроса клавиатуры может использоваться...". Можно этот вопрос и несколько иначе рассмотреть, но с точки зрения эмуляции и такое описание (за которое респект Pykу) вполне подойдет.

CityAceE
31.12.2018, 13:33
ivagor, вот-вот я как раз про это и говорю:

Для опроса клавиатуры может использоваться один из трех методов:


Порты A, младшая половина C и B программируются на ввод. Нажатая клавиша определяется по положению нулей в обоих портах. Нажатая клавиша располагается на пересечении положения нулей.
Порт A и младшая половина C программируются на вывод, порт B - на ввод. Осуществляется сканирование методом бегущего "0" путем записи в порты A и C и анализа порта B.
Порт A и младшая половина C программируются на ввод, порт B - на вsвод. Осуществляется сканирование методом бегущего "0" путем записи в порт B и анализа портов A и C.


У меня в эмуляторе как раз и реализована эмуляция под первый метод, который используется в "Загрузчике". Но я не могу найти информацию, при каких значениях, что получаем в соседних портах для сканирования методом бегущего нуля.

CityAceE
31.12.2018, 14:26
Всё, дошло!

ivagor
31.12.2018, 14:41
Для повышения удобства отладки можно воспользоваться таким тестиком. Параллельно можно запустить emu или emu80 и сравнивать результаты. Из исходника думаю понятно, что там делается, но на всякий случай: 91 - "метод 3"; 82 - "метод 2".

svofski
31.12.2018, 16:05
CityAceE, прошу прощения за телеграфный стиль - я писал с телефона. У 8255 есть режим быстрого сброса/установки бит в порту C. Он подробно описан в документации и эмуляторах, поэтому вдаваться в подробности здесь смысла нет.

Пока не изучал подробно сорцы, но сразу обратил внимание на то, что нету привязки к времени и если где-то принудительно не задан VSYNC, то программа молотит на 100% ядра. Особо не вдаваясь в pygame, с которым я знаком шапочно, попробовал сделать в основном цикле вот так:



clock = pygame.time.Clock()
while running:
cpu.core()
if cpu.ticks > INT_TICKS:
cpu.ticks = 0
if cpu.scr_upd:
cpu.pygame.display.flip()
clock.tick(50)
cpu.scr_upd = False


Если я его правильно понял, clock.tick(fps) вызывает SDL_Delay() если его вызывают чаще, чем указанный в параметре fps. Теперь эффективность кода можно оценивать по загрузке проца.

svofski
31.12.2018, 20:57
В основном модуле предлагаю убрать часть, которая проверяет запись в экран и рисует пиксели поштучно. Вместо нее добавить вот такое шаманство. Честно скажу, долго подбирал ;)


def blitsurface():
mem = np.reshape(memory[0x9000:0xc000], (256,48), 'F')
bits = np.unpackbits(mem) * 255
pygame.surfarray.blit_array(screen, np.reshape(bits, (256,384)).T)


И в основном цикле, соответственно:


while running:
cpu.core()
if cpu.ticks > INT_TICKS:
cpu.ticks = 0
cpu.blitsurface()
cpu.pygame.display.flip()
n = clock.tick(50)


Все это в приаттаченном к #1 (https://github.com/CityAceE/sPycialist/issues/1) патче, чтоб быстро попробовать.

CityAceE
01.01.2019, 05:07
Все это в приаттаченном к #1 патче, чтоб быстро попробовать.

Сравниваем. Слева - v0.3, справа - твой вариант.

https://pic.maxiol.com/images/1546225871.90463878.zoo.gif https://pic.maxiol.com/images/1546308438.90463878.zoosvofski.gif

CityAceE
01.01.2019, 09:33
У 8255 есть режим быстрого сброса/установки бит в порту C.
Да, но здесь не тот случай. До меня почему-то сразу не могло дойти, что порты A, B и C соединены друг с другом через клавиатуру. И в случае с бегущим нулём мы программируем один порт на ввод, а второй на вывод. После чего отправляем в порт байт и ловим его на другом порту. Если кнопки нажаты не были, то байт приходит неизменным, а если нажимались, то соответствующий бит изменяется. Вот и вся премудрость. Но у меня в эмуляторе пока реализовано только считывание данных с портов. Таким образом метод бегущего нуля пока не работает.


обратил внимание на то, что нету привязки к времени и если где-то принудительно не задан VSYNC, то программа молотит на 100% ядра.
Да, никакую задержку я осознанно не ставил, так как замедлять тут нечего - чем быстрее работает скрипт, тем неограниченно быстрее будет крутиться эмулятор. В холостую тут ничего не крутится и процессор больше положенного не грузит. В настоящий момент на моих компьютерах эмулятор не догоняет по скорости реальный Специалист.


В основном модуле предлагаю убрать часть, которая проверяет запись в экран и рисует пиксели поштучно. Вместо нее добавить вот такое шаманство.
Да, ничего не скажешь, элегантно получилось. Я с pygame вообще не знаком, поэтому о таком трюке даже не подозревал, но нутром чувствовал, что точками рисовать - это очень медленно. Получилось быстрее моего способа, несмотря на то, что у тебя выводится каждый кадр, даже если на нём и не было никаких изменений. Используя твой способ, я попробовал обновлять экран только в случае изменений на нём, но скорости это практически не добавило, хотя в сцене из ZOO можно пропустить порядка 1500 кадров, которые не имеют отличий. Всё сжирают проверки изменений. Однако, существенный минус твоего способа для меня лично состоит в дополнительном использовании numpy. Мне хотелось обойтись только одной внешней библиотекой pygame.

В общем, svofski, спасибо, что заморочился и помогаешь улучшить скрипт!

svofski
01.01.2019, 11:35
С наступившим!


Да, никакую задержку я осознанно не ставил, так как замедлять тут нечего - чем быстрее работает скрипт, тем неограниченно быстрее будет крутиться эмулятор. В холостую тут ничего не крутится и процессор больше положенного не грузит. В настоящий момент на моих компьютерах эмулятор не догоняет по скорости реальный Специалист.
Мне показалось, что тут есть некоторое противоречие, поэтому включаю режим капитана Очевидность и заранее прошу прощения. Задержка в современном понимании -- это засыпание процесса до какого-то события, обычно таймера. Если нет задержки, значит основной цикл крутится постоянно, не отдавая времени системе вообще. pygame.Clock.tick() отмеряет время между вызовами, и включает задержку, если они случаются чаще, чем указанный fps. То, что Python.exe не показывает 100% в Диспетчере задач, так это потому что проц многоядерный. Если ядра 4, там покажется 25%. 25% проца, но 100% одного ядра.

У меня с моими изменениями загрузка опускается до где-то 20%, хотя не всегда. То есть, если реальный Специалист делает INT_TICKS за 1/50 сек, у меня он уже догоняет реал и оставляет 1/5 времени. Убедиться в том, что в принципе tick() отдает время системе, можно сбросив INT_TICKS в два раза, например - тогда моя загрузка падает до 12%. Убедиться в том, что tick() именно уступает время, можно заменив tick() на tick_busy_loop(), который делает задержку, крутясь в цикле -- тогда загрузка не меняется.

Разумеется плюсы всего этого не получается ощутить, если эмулятор не успевает прогнать свои INT_TICKS за кадр. Надо либо апгрейдить компьютер, либо улучшать эмулятор. Эмулятор по-моему пока еще оставляет много возможностей для улучшения ;)


Используя твой способ, я попробовал обновлять экран только в случае изменений на нём, но скорости это практически не добавило, хотя в сцене из ZOO можно пропустить порядка 1500 кадров, которые не имеют отличий. Всё сжирают проверки изменений. Однако, существенный минус твоего способа для меня лично состоит в дополнительном использовании numpy. Мне хотелось обойтись только одной внешней библиотекой pygame.
По-моему экономить на рефреше экрана в эмуляторах это не лучший выбор, потому что он должен крутиться исправно в самом сложном случае. Эта игра нечасто обновляет кадры, а что делать в той, которая обновляет каждый кадр?

В любом случае, процедуры преобразования битмапов хорошо оптимизированы (хаха, это неправда, но на Питоне лучше не сделаешь), а битмапы тут крошечные. Обновление плоскости одним вызовом можно написать и на голом Питоне. Будет не так эффективно, но все равно должно получиться быстрее, чем ставить точечки поштучно.


В общем, svofski, спасибо, что заморочился и помогаешь улучшить скрипт!
Меня просто удивило, что для Питона Специалист оказался почти неподъемным. Хотя, казалось бы...

Я попробовал копнуть в сторону профайлинга, чего я с Питоном никогда не делал, но столкнулся с какой-то непробиваемой стеной устаревшего и глючного. Один раз получилось получить профайл, который загрузился в kcachegrind, но повторить я это не смог.

svofski
01.01.2019, 15:46
CityAceE, я попробовал еще несколько разных вещей, рассказываю:

1) вынес регистры в один bytearray со сквозной адресацией, чтобы избавиться от доступа к ним через globals()
Морально лично мне это приятней, но на моем компьютере объективный толк замерить трудно.
С флагами то же самое сделать оказалось сложней, и выходит по-моему слишком много обмена шила на мыло, поэтому их не пробовал.

2) попробовал вынести процессор в класс. Пришлось немного поработать поиском-заменой, но в общем это нетрудно. Я надеялся, что сужение скопа и избавление от globals ускорит эмулятор, но по-моему получилось наоборот. Может быть можно попробовать разобраться, в чем тормоз, но пока отложил.

3) заметил, что цикл опроса очереди событий запускается каждую инструкцию. Его прекрасно хватит одного раза на прерывание. Вот это сразу скинуло мне загрузку в два раза, то есть до 10% и спокойного вентилятора.

Файлы крошечные, поэтому просто сюда их прицеплю: 67516

CityAceE
01.01.2019, 16:15
Круто! Да, с опросом событий это ты очень верно подменил. Я даже не знаю почему я так бездумно сделал. Сделал, и даже в ту сторону и не смотрел больше. Вот что значит свежий взгляд со стороны!

Сравниваем изменения по сравнению с твоей предыдущей версией и реальной скоростью в Emu80:

https://pic.maxiol.com/images/1546308438.90463878.zoosvofski.gif https://pic.maxiol.com/images/1546348514.90463878.zoosvofskiplus3.gif

https://pic.maxiol.com/images/1546224855.90463878.zooemu80.gif

CityAceE
01.01.2019, 16:38
Если нет задержки, значит основной цикл крутится постоянно, не отдавая времени системе вообще.
В моём представлении без задержек эмулятор должен работать на полную катушку - всё должно летать. Но не летает! Потому что скорость выполнения скрипта меньше скорости работы реального Специалиста. Если бы она была выше и я бы поставил холостой цикл для задержки, тогда действительно процессор был бы нагружен постоянной дурной работой. А сейчас же он работает на полную, но не успевает сделать свою работу, так что и задержки ему не требуются. Это в моём представлении.


По-моему экономить на рефреше экрана в эмуляторах это не лучший выбор, потому что он должен крутиться исправно в самом сложном случае. Эта игра нечасто обновляет кадры, а что делать в той, которая обновляет каждый кадр?
Я с тобой полностью согласен! Но такой метод может сгодится, когда больше нет возможности что-то улучшить/ускорить. Но по твоим последним изысканиям, оказывается, что резервы для оптимизации её имеются :)


Обновление плоскости одним вызовом можно написать и на голом Питоне. Будет не так эффективно, но все равно должно получиться быстрее, чем ставить точечки поштучно.
Теперь, видя, такую скорость, никак не хочется её уменьшать. Это если вдруг удастся достигнуть превышения скорости, чтобы остались ресурсы для переписывания вывода плоскости без использования numpy, то тогда уже можно будет подумать об этом.


Меня просто удивило, что для Питона Специалист оказался почти неподъемным. Хотя, казалось бы...
И тем не менее уже что-то вырисовывается!

svofski
01.01.2019, 16:46
Так я и сам смотрел на это место два дня и мог бы обратить внимание, но в упор не видел. Заметил только когда напечатал табличку профайлера и посмотрел на число вызовов event.get().

Оставшееся всё вполне равномерно, вряд ли можно что-то принципиально улучшить в эмуляции процессора.

Самые популярные функции (отсортировано по количеству вызовов). По общему времени лидер - j<cond>, но у нее и количество вызовов большое, так что вроде пропорционально.


Tue Jan 1 16:19:19 2019 prof.out

28186074 function calls (28179897 primitive calls) in 24.967 seconds

Ordered by: call count

ncalls tottime percall cumtime percall filename:lineno(function)
8216227 1.807 0.000 1.807 0.000 i8080.py:338(inc_pc)
6406851 3.577 0.000 18.481 0.000 i8080.py:1128(core)
3540402 0.678 0.000 0.678 0.000 i8080.py:315(read_mem)
1598541 0.494 0.000 0.494 0.000 i8080.py:63(get_conditions)
1595476 3.415 0.000 5.177 0.000 i8080.py:865(b11000010)
1428592 1.612 0.000 1.925 0.000 i8080.py:669(b01000000)
1412212 1.731 0.000 1.731 0.000 i8080.py:170(or_a)
1398364 1.536 0.000 3.577 0.000 i8080.py:783(b10110000)
1394605 1.400 0.000 1.714 0.000 i8080.py:494(b00001011)
127088 0.092 0.000 0.092 0.000 i8080.py:353(dec_reg)
125392 0.141 0.000 0.256 0.000 i8080.py:536(b00000101)
117377 1.023 0.000 1.023 0.000 i8080.py:245(byte2mem)
83329 0.163 0.000 0.163 0.000 i8080.py:181(cp_a)
50121 0.084 0.000 0.084 0.000 i8080.py:148(and_a)
49003 0.100 0.000 0.157 0.000 i8080.py:469(b00111010)
48731 0.068 0.000 0.180 0.000 i8080.py:1016(b11100110)
47052 0.066 0.000 0.188 0.000 i8080.py:1040(b11111110)
41886 0.052 0.000 0.077 0.000 i8080.py:661(b01110000)
41688 0.047 0.000 0.056 0.000 i8080.py:477(b00000011)
39268 0.043 0.000 0.060 0.000 i8080.py:435(b00001010)
36239 0.041 0.000 0.120 0.000 i8080.py:799(b10111000)
21200 0.020 0.000 0.020 0.000 i8080.py:343(inc_reg)

ivagor
01.01.2019, 20:07
Вариант того, что можно сделать с клавиатурой (на базе v0.3). Изящества и красоты здесь нет, все тупо, но вроде работает. Еще в spyc_keyboard.py можно убрать fill_matrix. А по хорошему надо нормальный 8255.

CityAceE
02.01.2019, 11:32
ivagor, отличное решение с клавиатурой - не заполнять память последовательностью, а отдавать нужный байт при чтении соответствующей ячейки памяти! Спасибо за процедуру! Я её слегка оптимизировал и вынес за пределы эмулятора процессора. Также вынес за пределы эмулятора процессора и вывод экрана. Хочу чтобы эмулятор процессора был максимально чистым, и чтобы в случае чего его можно было задействовать в любом другом проекте.

Сейчас клавиатура работает везде, где не работала (тест С.Рюмика, игры Gold и Jet-Set Willi), однако остаются следующие проблемы:
1. Не работает кнопка НР.
2. Идёт залипание кнопок на пересечениях столбцов и рядов. То есть, если нажать две кнопки по диагоналям воображаемого прямоугольника на клавиатуре, то "нажмутся" ещё две по оставшимся углам прямоугольника.

Сходу понять почему это происходит и, соответственно, устранить у меня не получилось.

Также после оптимизации svofski сломался мой монитор регистров в i8080.py, вызываемый при запуске этого файла. Во-первых, теперь перепутаны местами значения регистровой пары AF. А во-вторых, приложение крашится на коде #21, при выводе содержимого памяти HL (или DE при коде #11). Опять же сходу сообразить что там не так не получилось.

Обновил версию 0.4 в первом посте и на гитхабе.

P.S. Приаттачил к сообщению пропатченый тест C.Рюмика, чтобы не ждать прохождения всех тестов, а чтобы сразу запускался тест клавиатуры. Там удобно увидеть все возникающие проблемы с клавиатурой.

ivagor
02.01.2019, 13:46
НР можно починить малой кровью, а чтобы диагональ не превращалась в квадрат нужно сильно переделывать.

Q-Master
02.01.2019, 13:58
2) попробовал вынести процессор в класс. Пришлось немного поработать поиском-заменой, но в общем это нетрудно. Я надеялся, что сужение скопа и избавление от globals ускорит эмулятор, но по-моему получилось наоборот. Может быть можно попробовать разобраться, в чем тормоз, но пока отложил.

Смысла нет ровным счетом никакого. Доступ к переменным класса и ф-циям класса в питоне несколько медленнее, т.к. везде передается self и еще там куча всякой магии.

svofski
02.01.2019, 14:40
CityAceE, я погорячился. Сейчас проще поправить монитор регистров. Инструкции работают за счет того, что у тебя операции с PSW и SP вынесены в отдельные обработчики. Но вообще я не прав, надо как-то переделать регистровый файл. Пока не могу придумать, как это получше сделать -- если регистры нумеровать подряд, 0=b, 1=c... то получается, что в отображении на регистровые пары перепутаны байты. А если как сейчас, то в принципе ок с учетом того, что я там сделано 1 ^ во всех адресациях регистров, но получается вот этот затык с перепутанным AF.

- - - Добавлено - - -


Смысла нет ровным счетом никакого. Доступ к переменным класса и ф-циям класса в питоне несколько медленнее, т.к. везде передается self и еще там куча всякой магии.
Вот. Я уже убедился в этом на практике.

CityAceE
02.01.2019, 14:43
НР можно починить малой кровью
Спасибо! Я чуть подправил:



elif vv55a_mode == 0x82:
if not (addr % 4):
return 0xff - kb_mem[0]
elif addr % 4 == 1:
if kb_ports[0] & kb_mem[0] or (kb_ports[2] & kb_mem[2]) % 0x10:
return 0xff - kb_ports[1]
else:
return 0xff - (kb_ports[1] & 2)
elif addr % 4 == 2:
return 0xff - kb_mem[2]




чтобы диагональ не превращалась в квадрат нужно сильно переделывать.
А в чём суть переделки?


А если как сейчас, то в принципе ок с учетом того, что я там сделано 1 ^ во всех адресациях регистров, но получается вот этот затык с перепутанным AF.
Бог с ним, с монитором - регистровую пару легко перевернуть в итоге при выводе. Просто я не совсем понял почему перемена местами A и F при их задании в начале файла всё крашит. Не стал разбираться и бросил. А вот 1 ^ меня реально не очень порадовало - это же дополнительная медленная логическая операция, которая будет замедлять эмуляцию инструкций. Но опять же всё оставил так, как ты сделал.

ivagor
02.01.2019, 14:52
Сейчас все нажатия сливаются в 2 с половиной байта, по которым можно правильно определить одновременные нажатия только в пределах ряда или колонки. Вместо этих 2.5 байт надо делать матрицу 12x6+1 бит или 12x7. Содержимое keys при этом целесообразно поменять на пары № колонки, № столбца.

svofski
02.01.2019, 15:05
Бог с ним, с монитором - регистровую пару легко перевернуть в итоге при выводе. Просто я не совсем понял почему перемена местами A и F при их задании в начале файла всё крашит. Не стал разбираться и бросил. А вот 1 ^ меня реально не очень порадовало - это же дополнительная медленная логическая операция, которая будет замедлять эмуляцию инструкций. Но опять же всё оставил так, как ты сделал.
Вот я не могу придумать, как сделать так, чтобы индексы регистров в bytearray были бы b = 0, c = 1, ... но при этом словная memoryview от того же массива была бы не перевернутой. Медленность операции 1 ^ мне кажется преувеличенной, но элегантности в этом нет. Факт.

Перемена местами A и F все крашит потому, что инструкции, которые ссылаются по индексу на A (например inr a), начинают ссылаться на F.

CityAceE
02.01.2019, 15:30
В общем, у меня пока нет никаких полезных мыслей ни по регистрам, ни по клавиатуре. Зато эмулятор стал вполне играбельным:

https://pic.maxiol.com/images/1546432146.90463878.adskok.gif

ZEman
02.01.2019, 16:17
В общем, у меня пока нет никаких полезных мыслей ни по регистрам, ни по клавиатуре. Зато эмулятор стал вполне играбельным:
без звука только.
не планируете добавить эмуляцию звука или сильно тормозить будет после этого ?

эммм......
поспешил с выводами малость.

как вообще запустить ЭТО из виндовса ?
то-ли то что у меня десятка, то-ли ещё что, но запустить не выходит никак.
создал в папке эмулятора батник с ключом "python spycialist.py" и всёравно тишина.

CityAceE
02.01.2019, 16:42
не планируете добавить эмуляцию звука
На эмуляцию звука не остаётся ресурсов пока.


как вообще запустить ЭТО из виндовса ?
1. Скачиваем с официального сайта (https://www.python.org/downloads/) и устанавливаем интерпретатор Python 3.
2. Устанавливаем пару требуемых библиотек, подав в консоли команду:


pip install pygame numpy

3. И только после этого запускаем батник.

ivagor
02.01.2019, 20:01
Модернизированный тест клавиатуры (https://zx-pk.ru/threads/29897-spycialist-emulyator-pk-spetsialist-na-python.html?p=993049&viewfull=1#post993049) (мой, не Рюмика).
1) Он будет нормально показывать не только на ч/б специалистах, но и на цветных
2) Нет миганий/морганий
3) Нет щелчков/потрескиваний
4) Не используются процедуры ПЗУ (но используется шрифт из ПЗУ, можно и свой шрифт прицепить при необходимости)
5) Добавил еще один вариант теста (82+91)
6) Развернул порядок вывода слева-направо, чтобы удобнее было сверять с картинкой (https://pic.maxiol.com/?v=1545099129.3254906935.specialist.png)

ivagor
03.01.2019, 08:40
Еще один пример того, как не надо программировать на питоне (да и вообще так не надо программировать). Также я поленился редактировать keys и вместо этого транслирую его содержимое в индексы и маски.

- - - Добавлено - - -

Caps Lock имхо лучше заменить на какую-нибудь другую клавишу, например Ctrl, а то сейчас приходится по два раза нажимать.

CityAceE
03.01.2019, 08:41
Сейчас все нажатия сливаются в 2 с половиной байта, по которым можно правильно определить одновременные нажатия только в пределах ряда или колонки.
Сделал прототип, чтобы клавиши не слипались. И оно работает. Но надо как-то теперь это переписать, чтобы было красиво, так как сейчас это угодливое нагромождение if'ов.


Еще один пример
Сделали практически синхронно :) И довольно похоже. Я тоже не редактировал keys. И обошёлся только одной таблицей (матрицей).

Просто для сравнения предлагаю свой вариант. Но у меня помимо всего прочего ещё и НР снова отвалился.

ivagor
03.01.2019, 09:10
Эксперимент - переделал свой вариант на циклы, теперь по крайней мере на (начальный) студенческий уровень тянет. Сейчас сравним :)

- - - Добавлено - - -

CityAceE, спасибо за мотивацию ознакомления с чем-то полезным, хоть питон чуть-чуть увидел.

ZEman
03.01.2019, 10:05
2. Устанавливаем пару требуемых библиотек, подав в консоли команду:
Код:

pip install pygame numpy

в какой консоли надо это писать ?
в командной строке ? - пробовал не выходит, пишет не является командой бла..бла..бла..
как нужно правильно запускать ?

CityAceE
03.01.2019, 10:38
Эксперимент - переделал свой вариант на циклы, теперь по крайней мере на (начальный) студенческий уровень тянет. Сейчас сравним
Вызов принят :) И на выходе у меня получилось почти слово в слово (см. вложение), хотя всё же самою чуточку короче.


хоть питон чуть-чуть увидел.
Я не устаю восхищаться крутизной Питона. Очень простой для понимания, для написания и для отладки. И даже зная, что это не тот язык, на котором нужно писать эмулятор, я всё равно решил попробовать.

CityAceE
03.01.2019, 10:47
в командной строке ?
Да, в командной строке.


как нужно правильно запускать ?
Python должен быть прописан в системных переменных (это делается по умолчанию, но требует перезагрузки компьютера после установки) , чтобы если в командной строке из любого места подать команду python, вызывался бы интерпретатор языка. В противном случае при запуске эмулятора нужно указывать полный путь к python.exe.

ivagor
03.01.2019, 10:56
CityAceE, режим 91 надо маленько починить.

не устаю восхищаться крутизной Питона
Если его системно изучать, то видна крутизна, а если как я пытаться переложить приемы из C или Паскаля, то вылезают всякие странности.

svofski
03.01.2019, 11:51
Если его системно изучать, то видна крутизна, а если как я пытаться переложить приемы из C или Паскаля, то вылезают всякие странности.
Вот новый SICP на примере Питона. Оригинал на Scheme, если хочется серьезного ада, то лучше оригинал.
https://wizardforcel.gitbooks.io/sicp-in-python/content/1.html
Увы, я не знаю насколько они друг с другом связаны. Потому что оригинал использует Scheme, (синтаксис (которой (весьма 'прост))), и сразу окунает читателя в собственно программирование. А эта похоже всю дорогу обсуждает гротескные изыски питонных конструкций.

ivagor
03.01.2019, 12:18
Извините за оффтоп.

Вот новый SICP на примере Питона.
Прикольный пример с Шекспировскими словами, даже попробовал. Наверно на питоне было бы удобно писать текстовые адвентюры, если бы это еще было актуально.

CityAceE
03.01.2019, 13:08
Наверно на питоне было бы удобно писать текстовые адвентюры
На Питоне удобно писать вообще всё, что угодно. Но всё упирается в его самый главный недостаток: Питон - интерпретатор с соответствующим быстродействием. Зато насколько удобно писать и отлаживать, так как ничего не нужно компилировать. А если что-то нужно проверить, то всегда можно конструкцию закинуть в консоль и тут же получить результат.


режим 91 надо маленько починить.
Починил. Но хочу чуть доделать. И спасибо за тест клавиатуры - он очень пригодился при отладке.

svofski
03.01.2019, 18:19
Извините за оффтоп.

Прикольный пример с Шекспировскими словами, даже попробовал. Наверно на питоне было бы удобно писать текстовые адвентюры, если бы это еще было актуально.
Текстовые авантюры до сих пор выходят. Например,
https://8bitgames.itch.io/hibernated1
и ASMR-нутое прохождение https://www.youtube.com/watch?v=QppmkcOoHTI

Что делает язык пригодным к написанию текстовых игр я не знаю. В свое время считалось, что Лисп для этого в самый раз. Году в 95-м я решил попробовать написать текстовую игру на Лиспе, наслушавшись о том, какой он для этого приспособленный. Но мне не хватило тогда сил переломить себя в сторону функционального программирования. Не в последнюю очередь потому, что common lisp, к сожалению, позволяет писать почти процедурно. А книжка, которой я пользовался, хоть и хорошая технически, все же не была SICP-ом. Так что я идею до конца не осознал и из этой затеи ничего не вышло.

Но я думаю, что под приспособленностью Лиспа для обработки текстов и ИИ в его время понимали в основном то, что язык вообще способен оперировать с символьной строкой. Во времена Фортрана это рвало шаблон.

CityAceE
04.01.2019, 05:08
Починил и оптимизировал свой вариант клавиатуры и оптимизировал как мог вариант ivagor'а. Наверняка можно ещё больше что-то сократить и улучшить, но у меня уже больше мыслей нет. Мой вариант за счёт двух похожих процедур получился чуть длиннее, но мне он нравится больше тем, что таблица keys осталась без каких-либо изменений.

Хотел в своём варианте объединить две очень похожие процедуры get_port_82 и get_port_91, но так и не придумал как. Честно говоря, после всех этих оптимизаций, объединений и переносов я практически перестал понимать логику работы процедуры :)

Q-Master
05.01.2019, 00:18
На Питоне удобно писать вообще всё, что угодно. Но всё упирается в его самый главный недостаток: Питон - интерпретатор с соответствующим быстродействием. Зато насколько удобно писать и отлаживать, так как ничего не нужно компилировать. А если что-то нужно проверить, то всегда можно конструкцию закинуть в консоль и тут же получить результат.
PyPy, Cython, python-jit, C-extensions.
Cython мы на работе юзаем для написания экстеншнов.

CityAceE
20.01.2019, 02:54
В итоге на моём компьютере эмулятор идёт вот так:
https://pic.maxiol.com/images/1547942064.90463878.zoo50.gif https://pic.maxiol.com/images/1547942345.90463878.zoounl.gif

https://pic.maxiol.com/images/1546224855.90463878.zooemu80.gif

Есть подозрение, что всё упирается не в нехватку быстродействия, а во что-то другое. Возможно, я с тактами команд Intel 8080 где-то пролетел, хотя я всё перепроверял.

"Запустил" виртуальный Intel 8080 на 2.5 MHz, вместо штатных 2:

https://pic.maxiol.com/images/1547943161.90463878.zoo25.gif

Зарелизил версию 0.5 (первое сообщение + github)

Albom
30.12.2020, 22:50
Наткнулся на этот эмулятор, и он мне сильно понравился! Попробовал запустить - с небольшими правками получилось. Дополнительно сделал ресайз окна и изображения в нём (пока без соблюдения пропорций). Но не работает клавиатура и мусор в нижней части экрана. Естественно, буду разбираться сам. Но если у кого есть идеи - буду рад помощи. Проблема, скорее всего, связана с эмуляцией оборудования. Например, неточно описана работа с памятью/регистрами с учётом разрядности интерпретатора.
Скрин:
https://raw.githubusercontent.com/Albom/sPycialist/master/screenshots/Screenshot_from_2020-12-30%2021-34-20.png
При нажатии на F1 получаю скан 1073741882....
Версии ПО: pygame 2.0.1 (SDL 2.0.14, Python 3.8.5), архитектура: x64, ОС: Linux и Windows.
Репозиторий: https://github.com/Albom/sPycialist

hobot
31.12.2020, 12:35
CityAceE, а можно про игру с картинок подробнее рассказать, поскольку любые LR-подобные игры всегда интересны,
и сами по себе, и возможно дополнительной информацией.

Спасибо.
:redface:

CityAceE
31.12.2020, 14:58
мусор в нижней части экрана
Это связано с тем, что перед загрузкой и запуском игры, необходимо инициализировать системные переменные. Возможно, и клавиатура не работает поэтому же.


а можно про игру с картинок подробнее рассказать
Это же ZOO из Моделиста-Конструктора. Наверное, единственная серьёзная игра, которая была у всех, благодаря той публикации.

ivagor
31.12.2020, 15:03
Это же ZOO из Моделиста-Конструктора
Можно отметить, что были еще версии как минимум для вектора (йети, навороченный вариант с редактором уровней), ориона и корвета.

Albom
31.12.2020, 20:19
Клавиатуру пофиксил путём замены кодов клавиш на константы из pygame. Приведу код в порядок - сделаю коммит.
Мусор в нижней части скриншота также возникает при нажатии на EDIT (F9) в Мониторе. Интересно. :)
Со Специалистом дела не имел, хоть есть почти собранная плата. Поэтому такой эмулятор - отличный тренажёр для изучения архитектуры.

CityAceE
02.01.2021, 11:25
вариант с редактором уровней
Редактор уровней для Специалиста также публиковался в Моделисте-Конструкторе.


Поэтому такой эмулятор - отличный тренажёр для изучения архитектуры.
Очень рад, что, как минимум, одному человеку мой эмулятор оказался полезен!

Titus
02.01.2021, 13:13
Это же ZOO из Моделиста-Конструктора. Наверное, единственная серьёзная игра, которая была у всех, благодаря той публикации.
Пещера(Land) из того же Моделиста мне нравится больше, да и сделана она интереснее на мой вкус.

hobot
14.02.2021, 08:14
Пещера(Land) из того же Моделиста мне нравится больше, да и сделана она интереснее на мой вкус.
а где игра ? где
эта легендарная Пещера?
Или утеряна безвозвратно?